does anyone know of a way to dynamically render vue.js components? For example, I have a table component that should render different buttons based on the prop definition:
Vue.component('my_table', {
template: '#my_table',
props: {
data: Array,
columns: Array,
actions: Array
}
});
Ideally, my template would be defined in the tmpl value of my action object:
<tbody>
<tr v-for="entry in data">
<td v-for="key in columns">
{{entry[key.field]}}
</td>
<td v-for="action in actions">
{{ action.tmpl }}
</td>
</tr>
</tbody>
Here's my fiddle:
https://jsfiddle.net/zfya92dh/43/
Anyone have any ideas?
Thanks in advance!
<component :is="action.tmpl"></component>
Here is your fiddle revised.
const button1 = Vue.extend({
template:'<button class="btn btn-primary">View</buttton>'
})
const button2 = Vue.extend({
template:'<button class="btn btn-primary" #click="some_method">Delete</buttton>',
methods:{
some_method(){
alert('clicked')
}
}
})
And the relevant data.
data: {
actions: [
{
name: 'view',
label: 'View Company',
method: 'click',
tmpl: button1
},
{
name: 'delete',
label: 'Delete Company',
method: 'click2',
tmpl: button2
},
],
You just want to insert HTML instead of plain text? Change
<td v-for="action in actions">
{{ action.tmpl }}
</td>
to
<td v-for="action in actions" v-html="action.tmpl"></td>
However, if your HTML includes Vue markup, the markup will not be respected. You will need to create actual components to represent each of your configurations and then use :is to tell Vue which one to use at a given time. Vue doesn't use string-based templating, so there is no way to make passing a template string work.
Related
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>
Problem : I am trying to create a table component for my app which would be use by other components to render a table. It could have three possible cell values :
Text
HTML
Component
I am able to render all the above values but I am stuck at binding an event listener. What I am trying to achieve is something like this :
Pass a method and event which is to be binded to the component and the table should bind that with respective cell.
So for example :
TABLE JSON
{
"cell-1":{
"type":"html",
"data":"<h4>text-1</h4>",
"method": someMethod
}
}
TABLE COMPONENT
<tbody>
<template>
<tr>
<td >
<span
v-if="type == 'html'"
v-html="data"
v-on:click.native="$emit(someMethod)"
v-on:click.native="someMethod"
></span>
</td>
</tr>
</template>
</tbody>
Above is just a snippet of what I am trying, the table loops through the object passed and renders accordingly.
I have already tried
SO Solution 1
SO Solution 2
Please let me know if any more info is required.
The best way is to have the method/handler inside the parent component and then trigger is using the emit functionality such that in
TABLE COMPONENT
<tbody>
<template>
<tr>
<td >
<span
v-if="type == 'html'"
v-html="data"
v-on:click.native="$emit('trigger-handler', {method: 'method1', data: {}})"
></span>
</td>
</tr>
</template>
</tbody>
and in
Parent.vue
<table-component #trigger-handler="triggerHandler" />
inside script
export default {
data() {
},
methods: {
triggerHandler(payload) {
// payload is actually the object passed from the child
this[payload.method](payload.data); // call a specific method
},
method1(data) {
},
method2(data) {
},
method3(data) {
}
}
}
I'm trying to implement a vue2 modal as described in the vue docs at https://v2.vuejs.org/v2/examples/modal.html.
It looks something like this:
<tbody v-if="fields.length">
<tr v-for="(item, index) in fields">
<td>#{{ item.name }}</td>
<td><input type="checkbox" id="checkbox" v-model="item.active"></td>
<td>
<button id="show-modal" #click="showModal = true">Show Modal</button>
</td>
</tr>
<modal :item="item" v-if="showModal" #close="showModal = false">
<h3 slot="header">Hello World</h3>
</modal>
</tbody>
Vue.component('modal', {
template: '#modal-template',
data: function() {
return {
item: ''
}
}
});
While the button shows up and does pop up the modal, I get a warning that Property or method "item" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option., and when I inspect the modal component in the Vue dev tools, the item is still '', it's not populated by the :item binding.
What's the right way to pass the item object into the modal so we can use it in the window?
If you're passing item as a value from the parent to a child component, you cannot use data--you must use props instead!
Vue.component('modal', {
template: '#modal-template',
props: ['item'],
data: function() {
return {
}
}
});
There are 2 things:
You don't reference item in the data object instead you should reference it as a prop for the component props: ['item'].
The modal is not inside the loop's scope, which ends at the closing </tr> tag. You could change this so that the click action performs a method that takes the item and assigns it to a variable that is always passed in like a prop. This would be similar to how you are doing it now, you would just change showModal = true to openModal(item) then have a method that would set the appropriate 2 values. Something like this:
openModal(item) {
this.showModal = true;
this.modalItem = item;
}
...
data: () => {
modalItem: null
...
}
I'm still learning Vue.js and I have a minor issue:
I have a single file component with an array of checkboxes, and I have looked at the documentation for using multiple checkboxes, but the example there requires me to declare:
new Vue({
el: '#example-3',
data: {
checkedNames: []
}
})
Edit However I have set this up in my single file component within the <script> tag within data(), but it just checks/unchecks all boxes at once (but feeds back true/false correctly):
<script>
export default {
name: 'PhrasesDetail',
data () {
return {
game: '',
language: '',
checkedPhrasesArr: {
el: '#selection',
data: {
checkedPhrasesArr: []
}
}
}
},
...
</script>
The Question is where and how do I declare the checkbox array so that it reacts/recognises the individual elements?
These are my checkboxes:
<tr v-for="(keyTransPair, index) in this.language.data">
<td id="selection"><input type="checkbox" :id="index" v-model="checkedPhrasesArr"></td>
<td>{{index}}</td>
...
</tr>
I have assembled a complete example. (I don´t know anything about your language.data object so I´m just using fake data).
<template>
<div>
<!-- Your Checkboxes -->
<table>
<tr v-for="(keyTransPair, index) in language.data">
<td id="selection"><input type="checkbox" :value="keyTransPair" :id="index" v-model="checkedPhrasesArr"></td>
<td>{{index}}</td>
</tr>
</table>
<!-- Show the selected boxes -->
{{ checkedPhrasesArr }}
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data () {
return {
// Your language data
language: {
data: {
keyOne: "one",
keyTwo: "two",
keyThree: "three"
}
},
// The Checkbox data
checkedPhrasesArr: []
}
}
}
</script>
Note that the checkbox values are bound with :value="keyTransPair". You can change the value to anything you want. This value will be added to the array if the checkbox is checked.
By the way: You use <td id="selection"> within the v-for loop. So the id "selection" will not be unique. You should better use a class instead of an id here.
I've got this chunk of code in my controller to output everything I'd like to in the first row. The second row is going to be available upon a click action. However, I'm trying to make it so that when I click any row shown, it'll only display the 2nd row that given row. Not the 2nd row of all the others.
This is in my template.
{{#each chosenSomething as |something|}}
<tr class='main' {{action 'toggleHelp'}}>
<td>{{something.this}}</td>
<td>{{something.that}}</td>
<td>{{something.hey}}</td>
<td>{{something.you}}</td>
</tr>
{{#if isDisplay}}
<tr class='help'>
<td class='instruction'></td>
<td class='video'></td>
</tr>
{{/if}}
{{/each}}
This is my JS for the action.
toggleHelp() {
this.toggleProperty('isDisplay');
},
I know how I would approach this using jQuery, but I'd like to know the Ember way of doing such action. I thought about including index, but unsure how it should be written.
I would suggest making a component and handing the toggle within.
https://ember-twiddle.com/e6ccad3d2ffdc4a9d627db12a1317fa5?openFiles=templates.components.toggle-thing.hbs%2Ctemplates.components.toggle-thing.hbs
// toggle-thing.hbs
<header {{action 'toggleAnswer'}}>
{{data.question}}
</header>
{{#if open}}
<footer>
{{data.answer}}
</footer>
{{/if}}
// toggle-thing.js
import Ember from 'ember';
export default Ember.Component.extend({
// tagName: 'tr', // or whatever if you like
// classNames: ['thing', 'row', 'etc'], // smooth out the template a bit later...
open: false, // default
actions: {
toggleAnswer() {
this.toggleProperty('open');
console.log( this.get('open') );
},
},
});
// application.hbs or wherever you want to list the things
<ul class='thing-list'>
{{#each model as |thing|}}
<li class='thing'>
{{toggle-thing data=thing}}
</li>
{{/each}}
</ul>
has a 'thing' model and is pulling in some question answers to the model hook for example.
import Ember from 'ember';
var data = [
{
question: "How to be funny",
answer: "Stand on your head",
},
{
question: "How to be smart",
answer: "Just listen more often.",
},
];
export default Ember.Route.extend({
model() {
return data;
},
});
The Ember guide has a toggle example: https://guides.emberjs.com/v2.13.0/tutorial/simple-component/
Augment each element of chosenSomething to include the open/close state.
You'll then be able to have:
{{#each chosenSomething as |something|}}
<tr class='main' {{action 'toggleHelp' something}}>
<td>{{something.this}}</td>
<td>{{something.that}}</td>
<td>{{something.hey}}</td>
<td>{{something.you}}</td>
</tr>
{{#if something.isDisplay}}
<tr class='help'>
<td class='instruction'></td>
<td class='video'></td>
</tr>
{{/if}}
{{/each}}
and the JS:
toggleHelp(something) {
Ember.set(something, 'isDisplay', !something.isDisplay);
}
If you don't want to modify the original data, you can have a computed property generate the appropriate data such as:
chosenSomethingWithState() {
return (this.get('chosenSomething') || []).map((item)=>{isDisplay:false, item})
}.property('chosenSomething')
and adjust the template accordingly