Using component with one props - vue.js

I am reading Vuejs documentation and I can say it is confusing. without running example or complete examples they have snippets, I am trying to understanding about using one prop instead of using a each for every property. I am trying to make a example from the docs but I cannot. Following is my code.
Vue.component('blog-post', {
props:['post'],
template:`
<div class="blog-post">
<h3>{{ post.title }}</h3>
</div>
`
})
new Vue({
el : '#app',
data: {
}
})
HTML
<div id="app">
<blog-post
v-for="post in posts"
v-bind:key="post.id"
v-bind:post="post"
></blog-post>
</div>
The error I am getting is "[Vue warn]: Property or method "posts" is not defined on the instance but referenced during render". I am confused on how one value can be used to show many objects.

props:['post'] check this plural. I figure that you are passing an array of posts to the component, and inside that component iterate over it.

You need top pass data.
Vue.component('blog-post', {
props:['post'],
template:`
<div class="blog-post">
<h3>{{ post.title }}</h3>
</div>
`
})
new Vue({
el : '#app',
data: {
posts: [
{
title: 'post-1'
},
{
title: 'post-2'
},
{
title: 'post-2'
}
]
}
})
so now
<blog-post
v-for="post in posts"
v-bind:key="post.id"
v-bind:post="post"
></blog-post>
you can use v-for and it will take posts data and iterate through it and pass single post to the component and component will use that property and render html.
here when blog-post will receive post it will be this [ v-bind:post="post" ]
{
title: 'post-1'
}
and it will use its post.title
v-for it will iterate this blog-post component [in our case 3 times] and pass each post object which is in array as post property of blog-post component.
if any doubts please comment.

Not sure I understand the question but I'll try to help:
The reason you get the error is because you are looping posts, but you haven't defined them anywhere, so you need to change your main instance data:
new Vue({
el : '#app',
data: {
posts: [{author: "someone", text: "something", title: "Some post"}]
}
})
There is a shorthand for v-bind, so instead you can write:
<div id="app">
<blog-post
v-for="post in posts"
:key="post.id"
:post="post"
></blog-post>
</div>

Related

Do all imported arrays in Vue.js have to be reactive?

I'm importing a simple array of 4 strings called QUIZ_DATA into my Vue.js component:
["Question1?", "Question number 2?", "This is number 3!", "And # 4"]
These 4 questions are static and won't change. I don't need them to be reactive, so I import them into my component as QUIZ_DATA, but when I try to iterate through the array, it gives me an error that the property is non-reactive.
<template>
<div v-for="(question, index) in QUIZ_DATA">
{{ question }}
</div>
</template>
<script>
import QUIZ_DATA from "~/plugins/QuizData.js";
export default {
// Nothing here yet
}
</script>
The console says:
Make sure that this property is reactive.
I got rid of the error by copying the array into my component's data object, which feels like an unnecessary step, given that the array won't change:
data(){return {quizData: QUIZ_DATA}}
Is there a way of iterating through static objects without needing to copy them into the component's data() object first?
QUIZ_DATA must be a property of the component instance if you want to access it from within the template.
Non-reactive data can be assigned to the component instance in the created hook.
created() {
this.QUIZ_DATA = QUIZ_DATA;
}
You can return your array with computed property:
const QUIZ_DATA = ["Question1?", "Question number 2?", "This is number 3!", "And # 4"]
new Vue({
el: '#demo',
computed: {
questions() {
return QUIZ_DATA
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="demo">
<div v-for="(question, index) in questions">
{{ question }}
</div>
</div>
Is there a way of iterating through static objects without needing to
copy them into the component's data() object first ?
Answer is Yes, ways to achieve :
By using computed property.
const QUIZ_DATA = ["Question1?", "Question number 2?", "This is number 3!", "And # 4"];
new Vue({
el: '#app',
computed: {
QUIZ_DATA() {
return QUIZ_DATA
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="(question, index) in QUIZ_DATA">
{{ question }}
</div>
</div>
As data is non-reactive we can use created hook.
let QUIZ_DATA = ["Question1?", "Question number 2?", "This is number 3!", "And # 4"];
new Vue({
el: '#app',
created: function () {
this.QUIZ_DATA = QUIZ_DATA;
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="(question, index) in QUIZ_DATA">
{{ question }}
</div>
</div>

How to search within nested objects

I have done my research trying to figure out how to achieve what I am describing below, however I had no luck.
In my Algolia index, some records have nested objects.
For example, title and subtitle attributes are of the following format:
title:
{
"en": "English title",
"gr": "Greek title"
}
I would like to execute queries only for a specific subset (in our example "en" or "gr") of these attributes, withoute "exposing" any facet in the UI — language selection would ideally be done “automatically” based on a variable (lang) passed to the Vue component with props. I am using Laravel Scout package with default Vue implementation, as described in documentation here.
My InstantSearch implementation is pretty simple, I am not defining anything specific regarding queries and searchable attributes, I am currently using all the default functionality of Algolia.
<template>
<ais-instant-search
:search-client="searchClient"
index-name="posts_index"
>
<div class="search-box">
<ais-search-box placeholder="Search posts..."></ais-search-box>
</div>
<ais-hits>
<template
slot="item"
slot-scope="{ item }"
>
<div class="list-image">
<img :src="'/images/' + item.image" />
</div>
<div class="list-text">
<h2">
{{ item.title }}
</h2>
<h3>
{{ item.subtitle }}
</h3>
</div>
</template>
</ais-hits>
</ais-instant-search>
</template>
<script>
import algoliasearch from 'algoliasearch/lite';
export default {
data() {
return {
searchClient: algoliasearch(
process.env.ALGOLIA_APP_ID,
process.env.ALGOLIA_SEARCH
),
route: route,
};
},
props: ['lang'],
computed: {
computedItem() {
// computed_item = this.item;
}
}
};
</script>
I would like to somehow pass an option to query “title.en” and “subtitle.en” when variable lang is set to “en”. All this, without the user having to select “title.en” or “subtitle.en” in the UI.
Update
Maybe computed properties is the path to go, however I cannot find how to reference search results/hits attributes (eg item.title) within computed property. It is the code I have commented out.
I think, you can use computed property. Just transform current item according to the current language variable.
new Vue({
template: "<div>{{ computedItem.title }}</div>",
data: {
langFromCookie: "en",
item: {
title: {
en: "Hello",
ru: "Привет"
}
}
},
computed: {
computedItem() {
const item = JSON.parse(JSON.stringify(this.item));
for (value in item) {
if (typeof item[value] === "object" && Object.keys(item[value]).includes(this.langFromCookie))
item[value] = item[value][this.langFromCookie];
}
return item;
}
}
}).$mount("#app")
<div id="app"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
If lang variable is available via props, you can check that inside list-text class and return {{title.en}} or {{title.gr}} accordingly by passing a dynamic lang value title[lang] like below
...
<div class="list-text">
<h2>
{{ item.title[lang] }}
</h2>
<h3>
{{ item.subtitle[lang] }}
</h3>
</div>
If you want to make a request according to lang prop when component mounts ,then you can make a request inside mounted() method then query like below
mounted() {
axios.get(`/getSomethingWithLang/:${this.item.title[this.lang]}`)
...
}

Vue.JS Passing object to child for use inside slot

I am trying to pass an object that is loaded within DataContainer into a slot, so that the user can customise the view.
<data-container silo-id="5">
<div slot="content"> <!-- I tried :data="siloData" here but no luck -->
Your current balance is {{data.balance}}
</div>
</data-container>
So DataContainer loads the resource via http and sets the value to its 'siloData' property.
DataContainer's template has no content of its own just a placeholder for the slot.
<template>
<div>
<slot name="content"></slot>
</div>
</template>
When I try this the text is not interpolated and just remains as {{siloData.balance}} to the browser.
I have tried some examples from Vue.JS site like the todo list, but I must admit utterly confused, maybe because this is not a collection, but just a single (albeit complex) object.
Hopefully someone can point me in the right direction.
Many thanks
Phil
You can use a $emit event
Vue.component('data-container', {
template: '#data-container',
data() {
return {
siloData: {}
}
},
mounted() {
this.siloData = { name: "Silo", balance: 10 } // loading data
this.$emit('silo-loaded', this.siloData)
}
})
new Vue({
el: '#app',
data() {
return {
data: {}
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.min.js"></script>
<div id="app">
<data-container class="card" #silo-loaded="val => data = val">
<div slot="content">
Your current balance is {{ data.balance }}
</div>
</data-container>
</div>
<template id="data-container">
<div>
<slot name="content"></slot>
</div>
</template>

How can I run some computed in vue.js 2?

My vue component (second component) like this :
<template>
<div class="row">
<div v-for="item in options">
<div class="panel panel-default panel-product">
....
</div>
</div>
<div>
<a href="#" class="panel-more">
<span>{{priceMin}} test {{priceMax}}</span>
</a>
</div>
</div>
</template>
<script>
...
export default {
...
computed: {
...mapGetters([
'getListByPrice', 'getPriceMin', 'getPriceMax'
]),
options() {
return this['getListByPrice']
},
priceMin() {
return this['getPriceMin']
},
priceMax() {
return this['getPriceMax']
},
},
...
}
</script>
If the code executed, the data shown does not match
If I console.log(this['getListByPrice']), there are 5 data. But shown in loops of more than 5 data
If I remove the code :
<span>{{priceMin}} test {{priceMax}}</span>
The result is correct
Why if I call priceMin and priceMax by computed, the result shown does not match?
I think that you don't need:
options() {
return this['getListByPrice']
},
priceMin() {
return this['getPriceMin']
},
priceMax() {
return this['getPriceMax']
},
Just use the items in ...mapGetters directly in your mustache tags. Also, use vue's chrome plugin to examine your vuex store.
You posted your vuex store so this comment addresses that. Your store is incorrect. price should be in your store, not priceMax and priceMin. Those are mutations of the state. Please read through the vuex docs.

Vue custom filtering input component

I'am trying to create a component that have 'just' an text input. String typed in this input will be used to filter a list. My problem is that I cannot handle how to share this filter string between my component and the main app that contains the list to filter.
I tried several things and most of the time I get the error :
Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value
So I looked Vuex but I thinks it cannot help in this case because I can have several filter component used in he same page for different list, and I don't want them to be synchronized ^^
Here is what I have:
The filter component
<script type="x/template" id="filterTpl">
<div>
<span class="filter-wrapper">
<input type="search" class="input input-filter" v-model.trim="filter" />
</span>
</div>
</script>
<script>
Vue.component('list-filter', {
props: {
filter: String
}
template: '#filterTpl'
});
</script>
And my main app:
<div id="contacts">
<list-filter :filter="filter"></list-filter>
<ul class="contacts-list managed-list flex">
<li class="contact" v-for="contactGroup in filteredData">
[...]
</li>
</ul>
</div>
<script>
var contactsV = new Vue({
el: '#contacts',
data: {
filter: "",
studyContactsGroups: []
},
computed: {
filteredData: function(){
// Using this.filter to filter the studyContactsGroups data
[...]
return filteredContacts;
}
}
});
</script>
Thanks for any help or tips :)
You can synchronize child value and parent prop either via explicit prop-event connection or more concise v-bind with sync modifier:
new Vue({
el: '#app',
data: {
rawData: ['John', 'Jane', 'Jim', 'Eddy', 'Maggy', 'Trump', 'Che'],
filter: ''
},
components: {
'my-input' : {
// bind prop 'query' to value and
// #input update parent prop 'filter' via event used with '.sync'
template: `<input :value="query" #input="updateFilter">`,
props: ['query'],
methods: {
updateFilter: function(e) {
this.$emit('update:query', e.target.value) // this is described in documentation
}
}
}
},
computed: {
filteredData: function() {
// simple filter function
return this.rawData.filter(el => el.toLowerCase()
.match(this.filter.toLowerCase()))
}
}
});
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<div id="app">
<my-input :query.sync="filter"></my-input>
<hr>
<ul>
<li v-for="line in filteredData">{{ line }}</li>
</ul>
</div>