Passing dynamic boolean props to a VueJS component - javascript

How can I attach dynamic properties to a VueJS Component using VuetifyJS?
I have the following VuetifyJS code example that creates a select field element:
<div id="app">
<v-app id="inspire" style="padding: 10px; ">
<v-select
v-model="selectField"
:items="items"
multiple attach chips>
</v-select>
</v-app>
</div>
new Vue({
el: '#app',
vuetify: new Vuetify(),
data () {
return {
selectField: '',
items: [
'item1',
'item2',
'item3'
],
booleanProperties: [
'multiple',
'attach',
'chips'
]
}
},
})
This creates a functional VuetifyJS select component, however I want to know how to pass the boolean props multiple, attach, chips to the select element as data properties so they do not have to be specified explicitly in the component declaration.
For example: I want to add the props: multiple, attach, chips defined within the data array element booleanProperties while still being able to define the component without having them specified. This way it works dynamically for any prop I pass.
Something similar to the following pseudocode example.
<v-select
v-model="selectField"
:items="items"
v-for="(booleanProperties) as boolProp">
</v-select>
How can I pass/specify the data props: multiple, attach, chips dynamically for the v-select element?
Here is a code example of what I am referring to.
https://codepen.io/deftonez4me/pen/NWRLWKE

You can simply use v-bind without specifying the key/property, and then passing an object into it, i.e. v-bind="additionalProps". As per VueJS v2 documentation on v-bind:
When used without an argument, can be used to bind an object containing attribute name-value pairs.
You can also merge your items binding into the object returned by additionalProps then, for brevity. Example based on your code.
new Vue({
el: '#app',
vuetify: new Vuetify(),
data () {
return {
selectField: '',
additionalProps: {
items: [
'item1',
'item2',
'item3'
],
multiple: true,
attach: true,
chips: true
}
}
}
});
<link href="https://cdnjs.cloudflare.com/ajax/libs/vuetify/2.3.16/vuetify.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.11/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuetify/2.3.16/vuetify.min.js"></script>
<div id="app">
<v-app id="inspire" style="padding: 10px; ">
<v-select
v-model="selectField"
v-bind="additionalProps">
</v-select>
</v-app>
</div>

You can go simply as this:
<div id="app">
<v-app id="inspire" style="padding: 10px; ">
<v-select
v-model="selectField"
:items="items"
:multiple="multiple"
:attach="attach"
:chips="chips">
</v-select>
</v-app>
</div>
Then you can declare the props as before:
props: [
'multiple',
'attach',
'chips'
]
Or a bit more specific like this:
props: {
multiple: {
type: Boolean
},
attach: {
type: Boolean
},
chips: {
type: Boolean
}
}

Related

Vuetify not showing when i use jQuery append

I'm having trouble using jquery append with vuetify. I can't write any vuetify code in my javascript. It wouldn't show me the vuetify code, but it should work. I think it has something to do with vuetify having to be restarted when you want to insert a new vuetify code.
new Vue({
el: '#app',
vuetify: new Vuetify(),
methods: {
createList() {
if (this.$refs.form.validate()) {
$('#lists').append(`
<v-card width="374" class="mx-10 my-12" elevation="5">
<v-system-bar color="${this.color.hexa}" dark><b>${this.name}</b></v-system-bar>
<v-form ref="form" lazy-validation>
<v-card-actions>
<v-text-field v-model="name" ref="name" class="mx-5" :rules="jobsTitleRules" label="Title" required></v-text-field>
<v-btn color="success" #click="addTitle">Add</v-btn>
</v-card-actions>
<ul style="padding: 0px;" id="jobs"></ul>
</v-form>
</v-card>`);
}
},
},
})
Searched everywhere and really can't find a solution. Anyone who can help?
As I mentioned in comments, jQuery append function was designed to add native HTML, native JS, or jQuery objects.
It is not possible to use it with Vue/Vuetify because you are trying to show virtual template components which are not native.
In your case you can use a list of Vue dynamic components.
First of all, you need to create your dynamic component (I simplified your template a little):
const externalForm = Vue.component('external-form', {
props: {
name: String,
color: String,
},
data() {
return {
internalName: this.name,
}
},
methods: {
addTitle() {
this.$emit("emit-title", this.internalName)
}
},
template: `
<v-card width="374" class="mx-10 my-12" elevation="5">
<v-system-bar :color="color" dark><b>{{name}}</b></v-system-bar>
<v-card-actions>
<v-text-field v-model="internalName" class="mx-5" label="Title"></v-text-field>
<v-btn color="success" #click="addTitle">Add</v-btn>
</v-card-actions>
</v-card>`,
});
When you need to pass some data to component, you should use props. For internal data you can use data section. And if you need to return some data, you can emit some event.
Now let's see how we can use it in your main template. Assume you already have some list of <v-list-item> components. You need to add some items into the tail of the list. This way, by example:
<v-btn #click="addItem" color="primary">Click here to add item</v-btn>
...
<v-list-item v-for="(item, key) in items" :key="key">
<component :is="item.component" v-bind="{...item.props}" #emit-title="pushToList"/>
</v-list-item>
And finally you need to define your main component:
new Vue({
el: '#app',
vuetify: new Vuetify(),
data() {
return {
items: [],
titles: []
}
},
methods: {
pushToList(val) {
this.titles.push(val)
},
addItem() {
this.items.push({ component: 'external-form', props: {
name: 'Some name',
color: '#' + Math.floor(Math.random() * 16777215).toString(16) //Just a random color HEX
}})
}
}
})
I hope now you can apply this method to your business logic.
CodePen playground is available here.

Elegant way to use dynamic components with props in Vue

Following case: I have an API outputting all the content for my Page, as well as its structure and so on (for better understanding, imaging an CMS which includes kind of a page builder, where an author can place components by drag and drop to generate pages content, which is delivered to the front-end by that api).
The structure of the api output would look something like:
{content: [
{component: hero, content: {...} },
{component: form, content: {...} },
...
]}
So to generate related content I would think of using dynamic components like:
<template v-for="item in content">
<component :is="item.component" />
</template>
However, doing so I would face the problem, that I have to add properties data onto my components somehow, which (as far as I could see) isn't described within the Vue documentation. So now I wonder how to pass props onto dynamic components, which have entirely different props (hero might have an image, form could have input-placeholders, and so on) - any ideas???
Take a look at v-bind https://v2.vuejs.org/v2/guide/components-props.html#Passing-the-Properties-of-an-Object (same as Vue 3).
Assuming your API includes a props property for each component, then you'd do this:
<component v-for="item in content" :is="item.component" v-bind="item.props"></component>
Nowadays it's necessary to state what version of Vue do you work with.
With Vue 2 you could do it like this:
Vue.component('FirstComponent', {
props: ['title', 'prop1_1'],
template: `
<div>
{{ title }}<br />
{{ prop1_1 }}
</div>
`
})
Vue.component('SecondComponent', {
props: ['title', 'prop2_1', 'prop2_2'],
template: `
<div>
{{ title }}<br />
{{ prop2_1 }}<br />
{{ prop2_1 }}
</div>
`
})
new Vue({
el: "#app",
data() {
return {
items: [
{
component: 'FirstComponent',
props: {
title: 'First component title',
prop1_1: 'this is prop 1_1'
},
},
{
component: 'SecondComponent',
props: {
title: 'Second component title',
prop2_1: 'this is prop 2_1',
prop2_2: 'this is prop 2_2',
},
},
]
}
},
template: `
<div>
<component
v-for="(item, idx) in items"
:key="idx"
:is="item.component"
v-bind="item.props"
></component>
</div>
`
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app"></div>

How can I use a component function in Vue to remove an object from an array in the parent's data?

I think it's easier for you to just see the relevant VueJS code and then I can explain it:
new Vue({
el: '#app',
data: {
history: [
{name: 'red', value: '#f00'},
{name: 'not red', value: '#ff0'},
{name: 'also not red', value: '#f0f'},
],
},
components: {
ColorItem: {
template:
`<div>
<input :value="name">
<div class="color-preview" :style="{backgroundColor:hex}"></div>
<span v-html="hex"></span>
<button #click="$emit('remove')">
<i class="fas fa-trash"></i>
</button>
</div>`,
props:
['mode', 'hex', 'name'],
methods: {
removeColor: function(index) {
console.log(index);
this.history.splice(index, 1);
}
},
}
},
// ...
}
I have objects (representing colors with names and values) in an array in a variable called history in my Vue app. I'm using v-for to create a new color-item component for each item in history:
<div v-for="(item, index) in history" :key="item.value">
<color-item mode="history" :name="item.name" :hex="item.value" #remove="removeColor(index)">
</color-item>
</div>
I'm trying to delete the color from the list, and I saw this beautiful example of how to use vue to remove items from a list, and it uses their position and splices it. I also saw this SO answer on getting the position using the map function, however pos is undefined for me, because e.hex is undefined, and using the inspector I think it's because Vue uses some sort of getter under the hood and doesn't just have the data there for me.
Before someone tells me to use the component template in the v-for loop, I need the template so I can reuse this for other lists of colors (for example, favorites).
I'm very new to Vue so pardon my improper wordings, and I appreciate all the help I can get learning this framework.
Generally, the child component should not be manipulating data of the parent component directly, especially via this.$parent.whatever. You should keep the boundaries of the components distinct, otherwise you'll make them tightly-coupled.
All the child component needs to do is emit a remove event that tells the parent that it should remove that item from its own data (which the parent owns).
In the child component:
<button #click="$emit('remove')">Remove</button>
Then in the parent component:
<div v-for="item of history">
<color-item :hex="item.hex" #remove="removeItem(item)"/>
</div>
methods: {
removeItem(item) {
this.history = this.history.filter(otherItem => otherItem !== item)
// or
this.history.splice(this.history.indexOf(item), 1)
}
}
The event handler for the remove event passes in the item to remove as an argument.
Since the parent component "owns" the history array, it should be the only component that mutates it. Once you start allowing random components to mutate data they do not own, then you start getting spaghetti code and it can be difficult to track down why a particular mutation happened and who mutated it.
Here is an example:
Vue.component('color-item', {
template: '#color-item',
props: ['name', 'hex'],
})
new Vue({
el: '#app',
data: {
items: [
{ name: 'red', value: '#f00' },
{ name: 'green', value: '#0f0' },
{ name: 'blue', value: '#00f' },
],
},
methods: {
removeItem(item) {
this.items.splice(this.items.indexOf(item), 1)
},
},
})
.name {
display: inline-block;
min-width: 50px;
}
.preview {
display: inline-block;
width: 15px;
height: 15px;
margin-right: 10px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<template id="color-item">
<div>
<span class="name">{{ name }}</span>
<div class="preview" :style="{ backgroundColor: hex }"></div>
<button #click="$emit('remove')">Remove</button>
</div>
</template>
<div id="app">
<div v-for="item of items">
<color-item :name="item.name" :hex="item.value" #remove="removeItem(item)"></color-item>
</div>
</div>
The better approach here is to use $emit from the child component and listening to the event in the parent component.
You should pass pos as an event then change the history data directly in parent component
In your removeColor function:
this.$emit('remove-color', pos)
Then in parent component you should have:
<ColorItem v-on:remove-color="removeColorFromHistory" />
And change your data in methods of the parent component:
methods: {
removeColorFromHistory(pos) {
this.history.splice(pos, 1);
}
}
(see documents)
$parent is meant for handling of edge cases, meaning unusual situations that sometimes require bending Vue’s rules a little. Note however, that they all have disadvantages or situations where they could be dangerous. *

Vue: v-model doesn't work with dynamic components

For example: <component v-model='foo' :is='boo' ...>.
foo's value stays the same during input.
I'm trying to solve the issue for quite a long time. I've checked lots of questions and threads but none of those helped me.
HTML doesn't work:
<component
:is="field.component"
:key="key"
:name="field.name"
v-for="(field, key) in integration_data"
v-model="field.value"
>
</component>
HTML works fine:
<input
:key="key"
:name="field.name"
v-for="(field, key) in integration_data"
v-model="field.value"
>
Vue controller:
export default {
init: function (init_data) {
return new Vue({
data: {
integration_data: [
{name: 'field_name0', component: 'input', value: ''},
{name: 'field_name0', component: 'input', value: ''},
]
},
});
}
}
You can't use input as a type of component and expect it to be a native input element. :is must name a component (which can contain an input, if you want).
Then you have to understand how v-model works on components:
So for a component to work with v-model, it should (these can be
configured in 2.2.0+):
accept a value prop
emit an input event with the new value
Putting that all together, you can use v-model with :is.
new Vue({
el: '#app',
data: {
integration_data: [{
name: 'one',
component: 'one',
value: 'ok'
}]
},
components: {
one: {
props: ['name', 'value'],
template: '<div>{{name}} and <input v-model="proxyValue"><slot></slot></div>',
computed: {
proxyValue: {
get() { return this.value; },
set(newValue) { this.$emit('input', newValue); }
}
}
}
}
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.min.js"></script>
<div id="app">
<component
:is="field.component"
v-for="(field, key) in integration_data"
:key="key"
:name="field.name"
v-model="field.value"
>
<div>{{field.value}}</div>
</component>
</div>
v-model has nothing to do with what you're possibly trying to do. It looks like you are trying to insert components dynamically. That's exactly what :is does. Now, to pass data to the component, you should use props.
For example:
Your Vue instance:
const vm = new Vue({
el: '#app',
data: {
exampleValue: 'This value will be passed to the example component'
}
})
Register a component:
Vue.component('example-component', {
// declare the props
props: ['value'],
template: '<span>{{ value}}</span>'
})
Then use it like this:
<example-component :value="exampleValue"></example-component>
Or:
<component :is="'example-component'" :value="exampleValue"></component>

Dynamically created input. Can't use $refs so how to get e.g. focus?

I have dynamically created inputs (list with elements, every element has own ID) for an edition.
All of them have v-if to be there only when the edit of the particular element is has been triggered.
Because of that, I can't use $refs as Vue does not see that in refs.
How can I solve it?
I really don't want to add jQuery for that or having to use vanilla JS every time when I need something like that which is quite often.
Usually, we have e.g. a span before editing, and would use v-show rather than v-if on it since we still need it after editing, and each input is coupled with its according span, so something like event.target.nextSibling.focus() will do the job.
I prefer event.target... to $refs as declaring $refs adds complexity to the component's structure while the other is just something only relevant within the click event.
If you really wanted to avoid the use of vanilla js (apart from for focusing) then I'd suggest you have to move your list elements into a component:
Vue.component('list-items', {
template:
`<div>
<button #click="edit">edit</button>
<input v-if="editing" ref="input" type="text" :value="value" #input="$emit('input', $event.target.value)">
</div>`,
props: ['value'],
data () {
return {
editing: false,
}
},
methods: {
edit () {
this.editing = !this.editing
if (this.editing) {
this.$nextTick(() => {
this.$refs.input.focus()
})
}
},
},
})
new Vue({
el: '#app',
data: {
list: [{
title: 'foo',
}, {
title: 'bar',
}]
},
})
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<div id="app">
<list-items v-model="item.title" v-for="item in list"></list-items>
<pre>{{ list }}</pre>
</div>

Categories