Dynamically pass component markup to Vue instance - javascript

I am building some software currently which will allow users to drop a HTML snippet into web pages and my VueJS stack will render blog posts dynamically.
I am trying to find a way to dynamically render component markup into a given <div> without declaring the Vue component markup within that <div> - to avoid confusion for customers.
This is an example of working code:
<div id="live-blogs" v-cloak>
<live-blog
v-for="blog in blogs"
:key="blog.id"
:title="blog.title"
></live-blog>
</div>
Vue.component('live-blog', {
props: ['id', 'title'],
template: '<div class="lb-entry">{{ title }}</div>',
});
const liveBlogs = new Vue({
el: '#live-blogs',
data: {
blogs: [],
},
methods: {
getLiveBlogs: function() {
request.get('/read/' + id)
.then(function (response) {
liveBlogs.blogs = response.data.data;
})
}
},
mounted() {
this.getLiveBlogs();
}
});
What I would like to do
I'd like to be able to strip out the component markup so my clients only have to copy and paste the following code. I am likely to add more components and functionality and don't want this embed growing in size.
Once the target <div> is detected, the javascript should handle the dynamic registration and rendering of component data.
<div id="live-blogs"></div>
<script type="text/javascript" src="/path/to/file/app.js"></script>
What I have tried so far
I have tried passing the component markup via this.$el.innerHTML = componentMarkup but it hasn't worked.
Is this possible using VueJS?

All you need to do is move the template from the DOM into the main component as a string template. As long as there is a <div id="live-blogs"></div> somewhere on the page, it'll just work.
Vue.component('live-blog', {
props: ['id', 'title'],
template: '<div class="lb-entry">{{ title }}</div>',
});
new Vue({
el: '#live-blogs',
template: `
<div>
<live-blog
v-for="blog in blogs"
:key="blog.id"
:title="blog.title"
/>
</div>`,
data() {
return {
blogs: [],
};
},
methods: {
getLiveBlogs() {
request.get('/read/' + id)
.then(response => {
this.blogs = response.data.data;
});
},
},
mounted() {
this.getLiveBlogs();
},
});

Related

How to use function that return value inside a template? Vuex, Vue

I'm using a template to get data of a json file, I use "v-for" to print all data, for example:
template: /*html*/
`
<div class="col-lg-8">
<template v-for="item of actividades">
<ul>
<li>{{ item.date }}</li>
<ul>
</template>
</div>
`,
But I need use functions, year() to modificate this information and return and result, for example:
template: /*html*/
`
<div class="col-lg-8">
<template v-for="item of actividades">
<ul>
<li>{{ year(item.date) }}</li>
<ul>
</template>
</div>
`,
The value {{ item.date }} print "2021-01-20" but I hope print "2021" using the function {{ year(item.date) }}
Code function year() using javascript:
year(date){
return String(date).substr(0, 4);
}
I tried use that code but is not working, appear this error:
That's my javascript code:
//VueEx
const store = new Vuex.Store({
state: {
actividades: [],
programas: [],
year: ""
},
mutations: {
llamarJsonMutation(state, llamarJsonAction){
state.actividades = llamarJsonAction.Nueva_estructura_proveedor;
state.programas = llamarJsonAction.BD_programas;
},
yearFunction(state, date){
state.year = String(date).substr(8, 2);
return state.year;
}
},
actions: {
llamarJson: async function({ commit }){
const data = await fetch('calendario-2021-prueba.json');
const dataJson = await data.json();
commit('llamarJsonMutation', dataJson);
}
}
});
//Vue
new Vue({
el: '#caja-vue',
store: store,
created() {
this.$store.dispatch('llamarJson');
}
});
Inside a template you can use functions defined as methods or computed. Technically, you can also use data to pass a function to the template, but I wouldn't recommend it. Not that it wouldn't work, but Vue makes anything declared in data reactive and there's no point in making a function (which is basically a constant) reactive. So, in your case:
new Vue({
el: '#app',
data: () => ({
actividades: [
{ date: '2021-01-20' },
{ date: '2020-01-20' },
{ date: '2019-01-20' },
{ date: '2018-01-20' },
{ date: '2017-01-20' }
]
}),
methods: {
year(date) { return date.substring(0, 4); }
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<ul>
<li v-for="(item, key) in actividades" :key="key">
{{ year(item.date) }}
</li>
</ul>
</div>
If, for some reason, you want to pass year as computed:
computed: {
year() { return date => date.substring(0, 4); }
}
But it's a convoluted construct (a getter function returning an inner arrow function) and this complexity doesn't serve any purpose. I'd recommend you use a method in your case, since it's the most straight-forward (easy to read/understand).
If you're importing the year function from another file:
import { year } from '../helpers'; // random example, replace with your import
// inside component's methods:
methods: {
year, // this provides `year` imported function to the template, as `year`
// it is equivalent to `year: year,`
// other methods here
}
Side notes:
there is no point in iterating through <template> tags which contain <ul>'s. You can place the v-for directly on the <ul> and lose the <template> (You should only use <template> when you want to apply some logic - i.e: a v-if - to a bunch of elements without actually wrapping them into a DOM wrapper; another use-case is when you want its children to be direct descendants of the its parent: for <ul>/<li> or <tbody>/<tr> relations, where you can't have intermediary wrappers between them). In your case, placing the v-for on the <ul> produces the exact same result with less code.
you should always key your v-for's: <ul v-for="(item, key) in actividades" :key="key">. Keys help Vue maintain the state of list elements, keep track of animations and update them correctly
I see you are trying to work with the Vuex store. And using mutation inside the template syntax.
Not sure if we can call mutation directly via HTML as the way you are doing. In the past when I tried to call a mutation, I would either:
Create an action which would commit that mutation and call that action wrapped inside a method through Vue, something like this:look for a method printSampleLog() that I defined here
Vue.component('followers', {
template: '<div>Followers: {{ computedFollowers }} {{printSampleLog()}}</div>',
data() {
return { followers: 0 }
},
created () {
this.$store.dispatch('getFollowers').then(res => {
this.followers = res.data.followers
})
},
computed: {
computedFollowers: function () {
return this.followers
}
},
methods:{
printSampleLog(){
this.$store.dispatch('sampleAction').then(res => {
this.followers = res.data.followers
})
}
}
});
const store = new Vuex.Store({
actions: {
getFollowers() {
return new Promise((resolve, reject) => {
axios.get('https://api.github.com/users/octocat')
.then(response => resolve(response))
.catch(err => reject(error))
});
},
sampleAction(context){
context.commit('sampleMutation');
}
},
mutations: {
sampleMutation(){
console.log("sample mutation")
}
}
})
const app = new Vue({
store,
el: '#app'
})
<script src="https://unpkg.com/vue"></script>
<script src="https://unpkg.com/vuex"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<div id="app">
<followers></followers>
</div>
Else create method w/o action in your Vue component committing the mutation directly, using this.$store.commit()
PS: Would recommend creating action around the mutation first, as it is a much cleaner approach.

Vuejs: data list not accessible from template in another template

So I have this set-up, which crashes on v-for construct of table-component. It shows an error: "Property or method "tablesList" is not defined on the instance but referenced during render". If I omit v-for table-component renders. If I access this data from "container" component all is fine. So the problem is in accessing data from child template in parent template.
What am I doing wrong?
let container = Vue.component("container", {
props: ["item"],
template: `<div class="container">
<table-component v-for="item in tablesList"></table-component>
</div>`
});
let table = Vue.component("table-component", {
props: ["item"],
template: `<div class="table">
this is a table
</div>`
});
let app = new Vue({
el: "#app",
data() {
return {
containersList: [],
tablesList: [{item:'item'}]
};
},
methods: {
anyMethod() {
}
}
});
</script>
You are using tablesList in container component But you defined it in app.
You need to add tablesList in container like below,
let container = Vue.component("container", {
props: ["item"],
data: () => {
return {
tablesList: [{item:'item'}]
}
},
template: `<div class="container">
<table-component v-for="item in tablesList"></table-component>
</div>`
});
NOTE: Use v-bind:key when use v-for.
You need to define tablesList in props => https://v2.vuejs.org/v2/guide/components.html#Passing-Data-to-Child-Components-with-Props

How to access data property of vue component

I am new to Vue.js and I think its amazing. I have been tasked to start implementing some vue components in our non-greenfield web application and I thought I would start by implementing some self-contained "widgets" that have to deal heavily with state in my work's rails app.
Its working great as a self-contained component but I want to load it with a data attribute so the component know what it needs to deal with. My Vue file looks like (I have redacted parts of this due to IP concerns):
<template>
<div class="card">
<div class="card-body">
${{ b.id }}
</div>
<div class="card-footer bg--blue-sky">
${{ b.amount }}
</div>
</div>
</template>
<script>
export default {
data: function () {
return {
errors: [],
b: {
id: null,
amount: null
}
}
},
// Fetches posts when the component is created.
created: function () {
jQuery.ajax({
url: "/api/b/" + '2' + ".json",
method: 'GET',
dataType: "json"
})
.then(response => {
this.b = response.b
})
.catch(e => {
this.errors.push(e)
});
}
}
</script>
<style scoped>
</style>
The component is registered with:
import FiDis from '../components/fi_dis.vue'
Vue.component('fi_dis', FiDis);
document.addEventListener('turbolinks:load', () => {
const fi_dis = new Vue({
el: '#bs',
components: { FiDis }
})
});
And in my html.erb code I create the components with:
<div id="bs" policy="2">
<fi_dis data-b-id="1"></fi_dis>
<fi_dis data-b-id="2"></fi_dis>
</div>
This all works flawlessly, and does exactly what I want it to do except for one thing. I want to access the data-b-id attribute within the created function of the component (i.e. replace the number '2' in the url of the ajax call above with the value form the attribute). In this way, I hope for the component to handle ANY "fi_dis" I choose, merely by specifying the b-id in the data attribute I want it to handle.
How can I achieve this?
You communicate data values passing props from parent component to child components.
So for example you should define which props your component is allowed to receive:
import FiDis from '../components/fi_dis.vue'
Vue.component('fi_dis', FiDis);
document.addEventListener('turbolinks:load', () => {
const fi_dis = new Vue({
el: '#bs',
components: { FiDis },
props['bId'],
created() { // This is a lifecycle method
this.printPropertyValue();
},
methods: {
// Your custom methods goes here separeted by commas
printPropertyValue() {
console.log(this.bId);
}
}
})
});
And the sintax for passing the data from the component implementation is using v-bind:propertyName or :propertyName (short hand).
<div id="bs" policy="2">
<fi_dis :bId="1"></fi_dis>
<fi_dis :bId="2"></fi_dis>
</div>

Vue 2.0 - How passing function to child component?

I have one issue. I want to pass function link to the child component. It's working but in HTML I get that code. It's correct how improve it?
I have Vue instance
app = new Vue({
... some code
data: {
onAppClose: null,
onAppSend: null
}
})
I want to add from global window any function. Or register function in Vue instance
app.onSend = () => console.log('data')
And pass this function to child
<div id="app">
<dynamsoft-component v-if="displayComponent"
:docs="docs"
:onAppSend="onSend"
:onAppClose="onClose"
></dynamsoft-component>
</div>
But I get this HTML template in console
<div id="app">
<div onappsend="()=>{}" onappclose="function (data) {
console.warn('dwdawad')
console.log('data')
}"></div>
</div>
You example code is not making a lot of sense - do you want to add a listener not a div or pass a function to a child component?`
I assume the latter. Vue has custom events for that .
Parent template:
<div v-on:appsend="someMethod" v-on:appclose="someOtherMethod"></div>
Parent component methods:
methods: {
someOtherMethod: function (data) {
console.warn('dwdawad')
console.log('data')
},
// ...
}
And then emit form the child:
this.$emit('appclose', {id: 'whatever'} /*pass data here*/)
Edit:
I still don't see how those functions would end up directly in the template, but the real problem is: HTML is not case-sensitive. so :onAppSend becomes :onappsend. You have to use kebap-case: :on-app-send. Vue will convert it to onAppSend in the component.
I have never used Vue.js before now..
But having a look at the how to on their site, this seems to work
In Vue style guide have recommendations about props naming
https://v2.vuejs.org/v2/style-guide/#Prop-name-casing-strongly-recommended
Vue.component('dynamsoft-component', {
props: ['onAppSend'],
template: '<button v-on:click="buttonclick">click me</button>',
methods: {
buttonclick(e){
// Check if onAppSend is defined.
if(Boolean(this.onAppSend)){
this.onAppSend();
}
}
}
})
new Vue({
el: '#app',
methods: {
onSend: function(){
console.log('child clicked');
}
}
});
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<div id="app">
<dynamsoft-component :on-app-send="onSend"></dynamsoft-component>
</div>

how to share data between components in VUE js (while creating list)

Could you please tell me how to share data between components in VUE js (while creating list).I have two components list components and add todo component.I want to add items in list when user click on add button.But issue is input field present in different component and list is present in different component
here is my code
https://plnkr.co/edit/bjsVWU6lrWdp2a2CjamQ?p=preview
// Code goes here
var MyComponent = Vue.extend({
template: '#todo-template',
props: ['items']
});
var AddTODO = Vue.extend({
template: '#add-todo',
props: ['m'],
data: function () {
return {
message: ''
}
},
methods: {
addTodo: function () {
console.log(this.message)
console.log(this.m);
//this.m =this.message;
},
},
});
Vue.component('my-component', MyComponent);
Vue.component('add-todo', AddTODO)
var app = new Vue({
el: '#App',
data: {
message: '',
items: []
},
});
The whole point of having a great MVVM framework is to let you have a view-model: a central store of all the state in your page/app/whatever. Components can emit events. You can have an event bus. But if you can save the day with a simple, global variable containing all your state, this is by far the cleanest, best solution. So just put your to-dos in an array, in a variable in global scope, then declare them in the data of every component that needs them. Here it is working in Plunkr.
markup
<div id="App" >
<add-todo></add-todo>
<my-component></my-component>
</div>
<template id="add-todo">
<div>
<input type="text" v-model="message">
<button #click="addTodo">Add todo</button>
</div>
</template>
<template id="todo-template">
<div>
<ul >
<li v-for="(item,index) in store.items">
{{item.message}}
</li>
</ul>
</div>
</template>
<script src="vue.js"></script>
<script src="script.js"></script>
code
// This is the magic store. This is all you need.
var vueStore = {items : []};
var MyComponent = Vue.extend({
template: '#todo-template',
data : function(){return {store : vueStore}}
});
var AddTODO = Vue.extend({
template: '#add-todo',
data: function () {
return {
message: '',
store : vueStore
}
},
methods: {
addTodo: function (event) {
this.store.items.push({'message' : this.message})
},
},
});
Vue.component('my-component', MyComponent);
Vue.component('add-todo', AddTODO)
var app = new Vue({
el: '#App',
data: {
store : vueStore
},
});
This is not a savage hack! We're being called to stop thinking about events, move up the food chain, and think about reactive pipes. Components don't care when or by who the central store gets updated. Vue takes care of it.
Here's the page on state management.
So you could use events and emit the created todo to the root vue instance.
I edited / forked your plunkr (I'm rather the fiddle type).
https://plnkr.co/edit/bnMiDmi30vsj3a8uROBK?p=preview
So I edited this line here, which listens for a custom event added and pushes the first argument to items.
<add-todo v-on:added='items.push(arguments[0])'></add-todo>
And also these lines, which emit the event. And i changed from the property m to the data message, because you shouldnt mutate props:
<input type="text" v-model="message">
<button #click="$emit('added', message)">Add todo</button>

Categories

Resources