Vuejs: data list not accessible from template in another template - javascript

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

Related

Vue bind child component data from parent

I need to bind my child component data (inputData) from parent component but it's not working i cannot find where is my mistake
app.js
let vm = new Vue({
el: "#app",
components: {
'modal-panel': modal,
'rich-select': richSelect,
'file-upload': uploader,
},
data(){ return {
isModalActive: false,
inputData: null
}} ,
methods: {
toggleModal(){
this.isModalActive = !this.isModalActive
},
modalData(){
this.inputData = 'Example Data'
}
}
});
Modal.vue
<template>
<input type="text" :value="inputData" >
</template>
export default {
name: 'modal',
props: ['inputData'],
mounted(){
console.log('modal Mounted')
}
};
inside my blade i'am calling modal component like this
<div class="container" id="app">
<modal-panel v-if="isModalActive" #close="toggleModal" :inputData="inputData"></modal-panel>
</div>
when i test that code all methods are working but inside Modal.vue input still not binding
You've to use the prop with kebab-case format as follows :
<modal-panel v-if="isModalActive" #close="toggleModal" :input-data="inputData"></modal-panel>

how to wrap a component content with html tag dynamically in vue

Hi i want to wrap the content of a component with some specific html tag let say button for this example.
i have a function which dynamically returns a value which i use as a prop, based on that i want to wrap the content of a component.
i know i could have achieved this way too <button><compA/></button> it does not solve my problem beacuse i need to change it in 100 places.
My expected result:
<button><div>press me i'm button</div></button>
<div>don't wrap me with button leave me as it is</div>
Note: :wrappwithbutton="" having true for 1st usage and false for 2nd usage
const localComponent = {
name:'first-comp',
template:`<div> {{text}}</div>`,
props:['wrappwithbutton','text'],
}
const app = new Vue({
el:'#app',
name:'app',
components:{'first-comp':localComponent},
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<first-comp :wrappwithbutton="true" text="press me i'm button"></first-comp>
<br/>
<hr/>
<br/>
<first-comp :wrappwithbutton="false" text="don't wrap me with button leave me as it is"></first-comp>
</div>
This is a perfect example for render functions. Instead of using a template you can use a render function to render the template for you. Read more about render functions
const localComponent = {
name:'first-comp',
props:['wrappwithbutton', 'text'],
methods: {
btnClick() {
if (this.wrappwithbutton) console.log('button')
}
},
render(h) {
return h(this.wrappwithbutton ? 'button' : 'div', [
h('div', this.text)
])
}
}
const app = new Vue({
el:'#app',
name:'app',
components:{'first-comp':localComponent},
});
Vue.config.productionTip = false
Vue.config.devtools = false
You can even go a step further and make your localComponent to be more dynamic with the parent passing a prop with the tag that should be rendered:
const localComponent = {
name:'first-comp',
props:['tag', 'text'],
methods: {
btnClick() {
if (this.wrappwithbutton) console.log('button')
}
},
render(h) {
return h(this.tag, [
h('div', this.text)
])
}
}
If you would like to have a single div and not two divs you can do:
render(h) {
if (this.tag === 'div') {
return ('div', this.text);
}
return h(this.tag ? 'button' : 'div', [
h('div', this.text)
])
}
This is my idea, but I think the template should have a more concise way of writing
const localComponent = {
name: "first-comp",
template: `
<template v-if="wrappwithbutton">
<button>
<div> {{text}}</div>
</button>
</template>
<template v-else>
<div> {{text}}</div>
</template>
`,
props: ["wrappwithbutton", "text"]
};
const app = new Vue({
el: "#app",
name: "app",
components: { "first-comp": localComponent }
});

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.

Vue state not updated with default injected value

If you click the button, you can see the value of state updated in the console, but it isn't updated in the page output. How can I make it work with a default injected value?
const Component = {
inject: {
state: {
default: () => ({
example: 1
})
}
},
template: `<div>
<div>{{ state }}</div>
<button #click="click">click</button>
</div>`,
methods: {
click() {
this.state.example += 1
console.log(this.state)
}
}
}
new Vue({
el: "#app",
components: {
Component
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<component></component>
</div>
Is it related to as Vue docs say "Note: the provide and inject bindings are NOT reactive. This is intentional. However, if you pass down an observed object, properties on that object do remain reactive."? I'm confused about the difference between the BINDINGS not being reactive but the OBSERVED OBJECT being reactive. Could you show an example to demo the difference?
Sorry, but it is not clear what you want - where's the provider for the "injection"? Why do you use inject in the same component as you use the value itself?
Here's your code, without inject:
1. Use the data attribute
const Component = {
data() {
return {
state: {
example: 1
}
}
},
template: `<div>
<div>{{ state }}</div>
<button #click="click">click</button>
</div>`,
methods: {
click() {
this.state.example += 1
console.log(this.state)
}
}
}
new Vue({
el: "#app",
components: {
Component
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<component></component>
</div>
Just use the data attribute - you can have a default value for example.
2. Use injection
inject is something completely different - it's a way to pass values from a provider to a consumer:
const Component = {
inject: ['state1'],
data() {
return {
state: {
example: 1
}
}
},
template: `<div>
<div>injected: {{ state1 }}</div>
<div>{{ state }}</div>
<button #click="click">click</button>
</div>`,
methods: {
click() {
this.state.example += 1
console.log(this.state)
}
}
}
new Vue({
el: "#app",
provide: {
state1: {
example1: 1
}
},
components: {
Component
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<component></component>
</div>
You can "skip" levels of components and use the provided value in components where you inject it - you don't have to pass it all the way down as props.
3. Create reactive inection
If you want to have reactive injection then you need to pass down something more complex:
const Component1 = {
inject: ['state2'],
data() {
return {
state: {
example: 1
}
}
},
template: `<div>
<div>injected: {{ state2.state2P }}</div>
<div>{{ state }}</div>
<button #click="click">click</button>
</div>`,
methods: {
click() {
this.state.example += 1
console.log(this.state)
}
}
}
new Vue({
el: "#app",
data() {
return {
state2: {
example2: 1
}
}
},
provide() {
// create an object (state2)
const state2 = {}
// define a property on the object (state2P), that
// has a get() function that always gets the provider's
// value you want to inject
Object.defineProperty(state2, 'state2P', {
enumerable: true,
get: () => this.state2,
})
// return the created object (with a property that always
// gets the value in the parent)
return {
state2
}
},
components: {
Component1
},
methods: {
parentClick() {
this.state2.example2 += 1
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<component1></component1>
<button #click="parentClick">PARENT CLICK</button>
</div>
I added a button to the template, so you can see that a method defined in the provider component's scope changes the value displayed in the consumer component's scope. (Also had to change the component's name, as Vue started to "whine" about using a restricted word.)

Dynamically pass component markup to Vue instance

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();
},
});

Categories

Resources