I have an input inside a component and wish to pass in an arbitrary number of
attributes depending on the situation in which it is used:
<input type="text" data-a="a" data-b="b" data-c="c">
I have a prop with the attributes but how can I loop them out to build attributes on an input?
Element has a dataset property. You can iterate it with Object.keys. And if you want to iterate all attributes of the element, use getAttributeNames() method.
var el = document.getElementsByTagName('input')[0]
// data-* attributes
Object.keys(el.dataset).forEach(key => {
console.log('data-' + key, el.dataset[key])
})
// all attributes
el.getAttributeNames().forEach(name => {
console.log(name, el.getAttribute(name))
})
<input type="text" data-a="a" data-b="b" data-c="c">
You can set the attributes of an element using the v-bind directive, which can be passed an object where each key is the attribute and value is the value for the attribute.
You say you are passing the attributes you'd like to bind as a prop. I'm going to assume your data structure since you didn't specify (if this isn't your data structure, you would need to create a computed property to format your data in this way):
{ 'data-a': 'a', 'data-b': 'b', 'data-c': 'c' }
And then, assuming the name of your prop is attrs, you would simply add the attributes to the input using v-bind like so:
<input type="text" v-bind="attrs">
Here's an example:
Vue.component('child', {
template: `<input type="text" v-bind="attrs">`,
props: {
attrs: { Object, default: () => ({}) }
}
});
new Vue({
el: '#app',
data() {
return {
myAttrs: {
'data-a': 'a',
'data-b': 'b',
'data-c': 'c',
}
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<child :attrs="myAttrs"></child>
</div>
You can use vuejs render function like
Vue.component('child', {
data() {
return {
attrs: {
'data-a': 'a',
'data-b': 'b',
'data-c': 'c'
}
}
},
render(h) {
return h('input', {
attrs: {
...this.attrs
}
})
}
});
This is how you can set attributes dynamically. Fiddle
Template =>
<div id="app">
<p class="text" v-bind="options">{{ message }}</p>
</div>
Script =>
new Vue({
el: '#app',
data: {
message: 'Hello Vue.js!',
propName: 'hello'
},
computed: {
options() {
return {
[`data-${this.propName}`]: this.message
}
}
}
})
Related
I using a computed object to conditionally return a list item. But, I get "ReferenceError: matchingEmployees is not defined" even though I defined it in the computed object. What am I missing? I checked for spelling errors and the reference to matchingEmployees in the directive matches the computed function. Thank you. You can see the entire code on this codepen.
new Vue({
el: '#app',
template:
`<div>
<h1>Vue.js Application</h1>
Search: <input v-model="searchStr" type="text" placeholder="Search Employee...">
<h2>All employees are listed below</h2>
<p v-for="employee in matchingEmployees">
{{ employee }}
</p>
</div>
`,
data: {
searchStr:'',
employees: [
'Alex Han',
'Ali Usman',
'Peter Parker',
'John Lee',
'Eva Holmes'
],
computed: {
matchingEmployees: function () {
return this.employees.filter((user)=> {
if (this.searchStr == ''){
return true;
} else {
return user.includes(this.searchStr)
}
})
}
}
}
})
computed property must be outside the data option, the data option must be a function that returns an object :
new Vue({
el: "#app",
data() {
return {
searchStr: "",
employees: [
"Alex Han",
"Ali Usman",
"Peter Parker",
"John Lee",
"Eva Holmes"
]
};
},
computed: {
matchingEmployees: function () {
return this.employees.filter((user) => {
if (this.searchStr == "") {
return true;
} else {
return user.includes(this.searchStr);
}
});
}
}
});
However adding template as an option is working I recommend to move to the html section in order to separate the content from the logic :
<div id="app">
<div>
<h1>Vue.js Application</h1>
Search: <input v-model="searchStr" type="text" placeholder="Search Employee...">
<h2>All employees are listed below</h2>
<p v-for="employee in matchingEmployees">
{{ employee }}
</p>
</div>
</div>
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.
I'm using VueJs2.
Component A consists of two components, B and C, and also contains a submit button.
each of the child components have an input element.
When A's submit button is clicked, I need to get input from B and C and submit them in a single
post request.
"Component A"
var A = {
components: {
'B',
'C',
},
template: `<input type="button" value="submit" id="submit" v-on:click="submitMethod" />`,
methods: {
submitMethod: function() {
}
}
}
"Component B"
var B = {
template: `<input type="text" id="fname" v-model="fname" />`,
data: {
fname: ''
}
}
"Component C"
var C = {
template: `<input type="text" id="lname" v-model="lname" />`,
data: {
lname: ''
}
}
How may I achieve this?
I understand your concern. These questions are not very well answered in the Vue community. If you are coming from React, there is, of course, some learning curve but you get to know all the best practices first hand.
In React, in this case Vue, In my component A, I will have state(data) for both the input in B and C. Then in my template I would do somethign like this.
<div>
<A :value="aValue" onChange="(value) => aValue = value"/>
<B :value="bValue" onChange="(value) => bValue = value"/>
<input type="button" value="submit" #click="myFucntion"/>
</div>
So now you have all the data in the top level(container) component. This will allow most of our component to be stateless and reusable. I understand this is a little verbose, but I think it is worth it.
You can use Event driven approach. this will be more elegant way to pass data from child to parent.
Vue.component('child', {
template: '#child',
//The child has a prop named 'value'. v-model will automatically bind to this prop
props: ['value'],
data: function() {
return {
internalValue: ''
}
},
watch: {
'internalValue': function() {
// When the internal value changes, we $emit an event. Because this event is
// named 'input', v-model will automatically update the parent value
this.$emit('input', this.internalValue);
}
},
created: function() {
// We initially sync the internalValue with the value passed in by the parent
this.internalValue = this.value;
}
});
new Vue({
el: '#app',
data: {
inputs: {
value1: 'hello1',
value2: 'hello2'
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.3/vue.js"></script>
<div id="app">
<p>Parent value: {{inputs}}</p>
<child v-model="inputs.value1"></child>
<child v-model="inputs.value2"></child>
</div>
<template id="child">
<input type="text" v-model="internalValue">
</template>
reference taken from here : vuejs update parent data from child component
author :
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 wanted to make a two-way data binding on my form input in Vue.js 2.3. However, I cannot use the v-model directive, because I want the data to be updated only on clicking the submit button. Meanwhile, the input value may be updated from another Vue method, so it should be bound to the data property text. I made up something like this jsFiddle:
<div id="demo">
<input :value="text" ref="input">
<button #click="update">OK</button>
<p id="result">{{text}}</p>
</div>
new Vue({
el: '#demo',
data: function() {
return {
text: ''
};
},
methods: {
update: function () {
this.text = this.$refs.input.value;
}
}
});
It works, but it does not scale well when there are more inputs. Is there a simpler way to accomplish this, without using $refs?
You can use an object and bind its properties to the inputs. Then, in your update method, you can copy the properties over to another object for display purposes. Then, you can set a deep watcher to update the values for the inputs whenever that object changes. You'll need to use this.$set when copying the properties so that the change will register with Vue.
new Vue({
el: '#demo',
data: function() {
return {
inputVals: {
text: '',
number: 0
},
displayVals: {}
};
},
methods: {
update() {
this.copyObject(this.displayVals, this.inputVals);
},
copyObject(toSet, toGet) {
Object.keys(toGet).forEach((key) => {
this.$set(toSet, key, toGet[key]);
});
}
},
watch: {
displayVals: {
deep: true,
handler() {
this.copyObject(this.inputVals, this.displayVals);
}
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.min.js"></script>
<div id="demo">
<input v-model="inputVals.text">
<input v-model="inputVals.number">
<button #click="update">OK</button>
<input v-for="val, key in displayVals" v-model="displayVals[key]">
</div>
If you're using ES2015, you can copy objects directly, so this isn't as verbose:
new Vue({
el: '#demo',
data() {
return {
inputVals: { text: '', number: 0 },
displayVals: {}
};
},
methods: {
update() {
this.displayVals = {...this.inputVals};
},
},
watch: {
displayVals: {
deep: true,
handler() {
this.inputVals = {...this.displayVals};
}
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.min.js"></script>
<div id="demo">
<input v-model="inputVals.text">
<input v-model="inputVals.number">
<button #click="update">OK</button>
<input v-for="val, key in displayVals" v-model="displayVals[key]">
</div>
You can use two separate data properties, one for the <input>'s value, the other for the committed value after the OK button is clicked.
<div id="demo">
<input v-model="editText">
<button #click="update">OK</button>
<p id="result">{{text}}</p>
</div>
new Vue({
el: '#demo',
data: function() {
return {
editText: '',
text: ''
};
},
methods: {
update: function () {
this.text = this.editText;
}
}
});
Updated fiddle
With a slightly different approach than the other answers I think you can achieve something that is easily scalable.
This is a first pass, but using components, you could build your own input elements that submitted precisely when you wanted. Here is an example of an input element that works like a regular input element when it is outside of a t-form component, but only updates v-model on submit when inside a t-form.
Vue.component("t-input", {
props:["value"],
template:`
<input type="text" v-model="internalValue" #input="onInput">
`,
data(){
return {
internalValue: this.value,
wrapped: false
}
},
watch:{
value(newVal){
this.internalValue = newVal
}
},
methods:{
update(){
this.$emit('input', this.internalValue)
},
onInput(){
if (!this.wrapped)
this.$emit('input', this.internalValue)
}
},
mounted(){
if(this.$parent.isTriggeredForm){
this.$parent.register(this)
this.wrapped = true
}
}
})
Here is an example of t-form.
Vue.component("t-form",{
template:`
<form #submit.prevent="submit">
<slot></slot>
</form>
`,
data(){
return {
isTriggeredForm: true,
inputs:[]
}
},
methods:{
submit(){
for(let input of this.inputs)
input.update()
},
register(input){
this.inputs.push(input)
}
}
})
Having those in place, your job becomes very simple.
<t-form>
<t-input v-model="text"></t-input><br>
<t-input v-model="text2"></t-input><br>
<t-input v-model="text3"></t-input><br>
<t-input v-model="text4"></t-input><br>
<button>Submit</button>
</t-form>
This template will only update the bound expressions when the button is clicked. You can have as many t-inputs as you want.
Here is a working example. I included t-input elements both inside and outside the form so you can see that inside the form, the model is only updated on submit, and outside the form the elements work like a typical input.
console.clear()
//
Vue.component("t-input", {
props: ["value"],
template: `
<input type="text" v-model="internalValue" #input="onInput">
`,
data() {
return {
internalValue: this.value,
wrapped: false
}
},
watch: {
value(newVal) {
this.internalValue = newVal
}
},
methods: {
update() {
this.$emit('input', this.internalValue)
},
onInput() {
if (!this.wrapped)
this.$emit('input', this.internalValue)
}
},
mounted() {
if (this.$parent.isTriggeredForm) {
this.$parent.register(this)
this.wrapped = true
}
}
})
Vue.component("t-form", {
template: `
<form #submit.prevent="submit">
<slot></slot>
</form>
`,
data() {
return {
isTriggeredForm: true,
inputs: []
}
},
methods: {
submit() {
for (let input of this.inputs)
input.update()
},
register(input) {
this.inputs.push(input)
}
}
})
new Vue({
el: "#app",
data: {
text: "bob",
text2: "mary",
text3: "jane",
text4: "billy"
},
})
<script src="https://unpkg.com/vue#2.2.6/dist/vue.js"></script>
<div id="app">
<t-form>
<t-input v-model="text"></t-input><br>
<t-input v-model="text2"></t-input><br>
<t-input v-model="text3"></t-input><br>
<t-input v-model="text4"></t-input><br>
<button>Submit</button>
</t-form>
Non-wrapped:
<t-input v-model="text"></t-input>
<h4>Data</h4>
{{$data}}
<h4>Update Data</h4>
<button type="button" #click="text='jerome'">Change Text</button>
</div>