wrap template component with his name - vue.js

I have components and I would like every component template wrapped inside a div with his name.
Vue.component('search', {
template: '<div>component template</div>'
html result
<div class="component-search">
<div>component template</div>
</div>

I can't imagine why you would want to be do that but anyhow you could do the following
Vue.component('search', {
name: 'search',
template: `<div :class="wrapperClass">
<div>component template</div>
</div>`,
computed: {
componentName() {
return this.$options.name
},
wrapperClass() {
return `component-${this.componentName}`
},
}
})

Related

How to pass and change index of array in vue?

I have the gist of how to do this, but I'm a beginner in vue, and I'm struggling with how to put it together. I need Control.vue to update the index in Exhibitor.vue. I know I'll have an $emit event happening in Control when I click on the button to pass the index data to the parent, and I'd have to use props to pass data from Exhibitor to its children, but how? I can't understand how to pass the index of an array with my code.
Exhibitor.vue
<template>
<div id="exhibitor">
<section class="exhibitor_info">
<h1 class="exhibitor_name">{{ exhibitors[index].firstName }} {{ exhibitors[index].lastName }}</h1>
<h2>Tag Number: {{ exhibitors[index].tagNum }} <em>{{ exhibitors[index].species }}</em></h2>
</section>
<div class="frame"><img :src="getImgUrl(exhibitors[index].picture)" alt="Exhibitor-Picture" class="image"></div>
</div>
</template>
<script>
export default {
name: 'Exhibitor',
data() {
return {
exhibitors: [],
index: 0
}
},
created: function() {
this.fetchExhibitors();
},
methods: {
fetchExhibitors() {
let uri = 'http://localhost:8081/exhibitor'
this.axios.get(uri).then(response => {
this.exhibitors = response.data
})
},
getImgUrl: function(pic) {
return require('../assets/' + pic)
}
}
}
</script>
Display.vue
<template>
<div id="display">
<exhibitor></exhibitor>
<buyer></buyer>
</div>
</template>
<script>
import Exhibitor from './Exhibitor.vue';
import Buyer from './Buyer.vue';
export default {
components: {
'exhibitor': Exhibitor,
'buyer': Buyer
}
}
</script>
Control.vue
<template>
<div id="control">
<display></display>
<button v-on:click="incrementLeft">Left</button>
<button v-on:click="incrementRight">Right</button>
</div>
</template>
<script>
import Exhibitor from './Exhibitor.vue';
import Display from './Display.vue';
export default{
props: ['exhibitors', 'buyers', 'index'],
data() {
return {
index: 0
}
},
methods: {
incrementRight: function() {
// Note that '%' operator in JS is remainder and NOT modulo
this.index = ++this.index % this.exhibitors.length
},
incrementLeft: function() {
// Note that '%' operator in JS is remainder and NOT modulo
if (this.index === 0) {
this.index = this.exhibitors.length - 1
} else {
this.index = --this.index % this.exhibitors.length
}
}
},
components: {
'display': Display
}
}
</script>
So you can get what you want to happen and there are two ways of making it happen that I can think of. First I will just clarify the terms relating to this because you seem to have them the wrong way around. Let's look at you tier structure which is like this:
Control.vue
contains: Display.vue
contains: Exhibitors.vue & Buyers.vue.
Therefore Control.vue is the parent of Display.vue which is the parent of Buyers.vue and Exhibitors.vue.
Anyway, What we need to do is control the array of exhibitors (and I guess buyers but you didn't include them in your code so I'll do likewise) which is in Exhibitors.vue from Control.Vue even though they don't have a direct parent child relationship. What I've done is set a prop that is passed to Display.vue which uses scoped slots to render the exhibitors from Exhibitors.Vue. Because the left and right buttons need to know when to stop going I have emitted the array length from Exhibitors.vue to Display.vue and again to Control.vue. It all works so heres some code.
//Control.vue
<template>
<div class="content">
<display v-on:finalLength="setIndexLimit" :i="index"></display>
<button #click="changeDown">Down</button>
<button #click="changeUp">Up</button>
<p>{{indLimit}}</p>
</div>
</template>
<script>
import Display from '#/components/Display'
export default {
components: {
Display
},
data: () => ({
index: 0,
indLimit: 0
}),
methods: {
changeUp() {
if (this.indLimit === this.index+1) {
this.index=0
}
else {
this.index ++
}
},
changeDown() {
if (this.index === 0) {
this.index = this.indLimit - 1
}
else {
this.index --
}
},
setIndexLimit(e) {
this.indLimit = e
}
}
}
</script>
and
//Display.vue
<template>
<div id="display">
<p>From Display</p>
<exhibitors v-on:ExLength="setLength">
<p>{{i}}</p>
</exhibitors>
<exhibitors>
<p slot-scope="{ exhibitors }">{{exhibitors[i].firstName}}</p>
</exhibitors>
<exhibitors>
<p slot-scope="{ exhibitors }">{{exhibitors[i].lastName}}</p>
</exhibitors>
<exhibitors>
<p slot-scope="{ exhibitors }">{{exhibitors[i].tagNum}}</p>
</exhibitors>
<exhibitors>
<p slot-scope="{ exhibitors }">{{exhibitors[i].species}}</p>
</exhibitors>
</div>
</template>
<script>
import Child from '#/components/admin/Exhibitors'
export default {
components: {
Exhibitors
},
props: [
'i'
],
data: () => ({
exhibitLength: null
}),
methods: {
setLength(e) {
this.exhibitLength = e
this.$emit('finalLength',e)
}
}
}
</script>
And finally:
//Exhibitors.vue
<template>
<div>
<slot :exhibitors="exhibitors"><p>I'm the child component!</p></slot>
</div>
</template>
<script>
export default {
data: () => ({
exhibitors: [
{
firstName: 'Joe',
lastName: 'Burns',
tagNum: 1,
species: 'ant'
},
{
firstName: 'Tom',
lastName: 'Yorke',
tagNum: 2,
species: 'bee'
},
{
firstName: 'Flit',
lastName: 'Argmeno',
tagNum: 3,
species: 'giraffe'
}
],
}),
mounted: function () {
this.$nextTick(function () {
let length = this.exhibitors.length
this.$emit('ExLength', length)
})
}
}
</script>
So as I said all that works, you can click the buttons and it will loop through the array contents. You can style it how you want wherever you like. However, it is a bit messy with props and slots and emits and whatnot just to relay a single index number and it would be far easier in my opinion to have a vuex store where 'index' is stored as a state item and can be used and changed anywhere without all the above carry on.

Pass data to vue component

i'm learning Vue.js right now, but i have a little problem on understanding a quite easy task ( maybe my idea of programming is too old ).
i've created a little component with this code.
<template>
<div class="tabSelectorRoot">
<ul>
<li v-for="(element,index) in elements" v-on:click="changeSelected(index)">
<a :class="{ 'selected': activeIndex === index }" :data-value="element.value"> {{ element.text }}</a>
</li>
</ul>
<div class="indicator"></div>
</div>
</template>
<script>
export default {
name: "TabSelectorComponent",
data () {
return {
activeIndex : 0,
elements: [
{ 'text':'Images', 'value': 'immagini','selected':true},
{ 'text':'WallArts', 'value': 'wallart'}
]
}
},
created: function () {
},
methods: {
'changeSelected' : function( index,evt) {
if ( index == this.activeIndex) { return; }
this.activeIndex = index;
document.querySelector('.indicator').style.left= 90 * index +'px';
this.$emit('tabSelector:nameChanged',)
}
}
}
</script>
and this is the root
<template>
<div id="app">
<tab-selector-component></tab-selector-component>
</div>
</template>
<script>
import TabSelectorComponent from "./TabSelectorComponent";
export default {
name: 'app',
components: {TabSelectorComponent},
data () {
return {
msg: 'Welcome to Your Vue.js App'
}
},
'mounted' : function() {
console.log(this)
//EventManager.eventify(this,window.eventManager);
/*this.register('tabSelector:changeValue',function(el){
console.log(el);
});*/
}
}
</script>
All of this renders in something like this
I'd like to reuse the component by varying the number of objects inside the list but i cannot understand how to accomplish this simple task
The basic way to communicate between components in Vue is using properties and events. In your case, what you would want to do is add an elements property to your TabSelectorComponent that is set by the parent.
TabSelectorComponent
export default {
name: "TabSelectorComponent",
props: ["elements"],
data () {
return {
activeIndex : 0
}
},
...
}
In your parent:
<tab-selector-component :elements="elementArray"></tab-selector-component>
This is covered in the documentation here.

Adding a "Default" image to my <img> (VUE)

I have an area where people can upload their own user-image. But if they do not, I would like to display a default one.
After some googling, I found I can do so by doing something like -
<img :src="creatorImage" #error="defaultAvatar">
But, I am not sure how to then created a method to pass the correct (default) image into it.
I did it with a computed property, like this:
<template>
<img :src="creatorImage" #error="imageError = true"/>
</template>
<script>
...
data() {
return {
imageError: false,
defaultImage: require("#/assets/imgs/default.jpg")
};
},
computed: {
creatorImage() {
return this.imageError ? this.defaultImage : "creator-image.jpg";
}
}
...
</script>
I would suggest creating a component for this, as it sounds like something that will be used on more places.
JsFiddle example
Component
Vue.component('img-with-default', {
props: ['defaultImg'],
data: function () {
return {
defaultAvatar: this.defaultImg || 'https://cdn0.iconfinder.com/data/icons/crime-protection-people/110/Ninja-128.png'
}
},
computed:{
userImage: function() {
if(this.uploadedImg != null) {
return this.uploadedImg;
} else {
return this.defaultAvatar;
}
}
},
template: '<img :src="userImage">'
})
And using the commponent would be
HTML
<div id="editor">
<img-with-default></img-with-default>
<img-with-default default-img="https://cdn3.iconfinder.com/data/icons/avatars-15/64/_Ninja-2-128.png" ></img-with-default>
</div>
JS
new Vue({
el: '#editor'
})
With this you have the default image.
If you want you can create a component that would display passed img src or the default one.
Component
Vue.component('img-with-default', {
props: ['imgSrc'],
data: function () {
return {
imageSource: this.imgSrc || 'https://cdn0.iconfinder.com/data/icons/crime-protection-people/110/Ninja-128.png'
}
},
template: '<img :src="imageSource">'
})
and to use it
HTML
<div id="editor">
<img-with-default></img-with-default>
<img-with-default img-src="https://cdn3.iconfinder.com/data/icons/avatars-15/64/_Ninja-2-128.png" ></img-with-default>
</div>

How do i get the ViewModel from Vue's :is

i have these components:
<template id="test-button-component">
<div class="test-button__container">
This is test button
<button #click="clickButton">{{buttonTitle}}</button>
</div>
</template>
<template id="test-button-component2">
<div class="test-button__container">
<button></button>
</div>
</template>
I try to use the Vue's :is binding to do a component binding by name as follow:
<div :is='myComponentName' ></div>
every time the myComponentName changed to other component, the new component will replace the old component. The thing i need is, is there any way i can get the instance of the component so i can get the view model instance of the currently bound component?
You can add a ref attribute (for example ref="custom") to the <div> tag for the dynamic component. And then reference the component instance via this.$refs.custom.
Here's a simple example where the data of the component gets logged whenever the value being bound to the is prop is changed:
new Vue({
el: '#app',
data() {
return {
value: 'foo',
children: {
foo: {
name: 'foo',
template: '<div>foo</div>',
data() {
return { value: 1 };
}
},
bar: {
name: 'bar',
template: '<div>bar</div>',
data() {
return { value: 2 };
}
}
}
}
},
computed: {
custom() {
return this.children[this.value];
}
},
watch: {
custom() {
this.$nextTick(() => {
console.log(this.$refs.custom.$data)
});
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.min.js"></script>
<div id="app">
<select v-model="value">
<option>foo</option>
<option>bar</option>
</select>
<div :is="custom" ref="custom"></div>
</div>
Note that the $data for the component reference by $refs.custom is getting logged inside of a $nextTick handler. This is because the bound component won't update until the parent view has re-rendered.

Fetch data in component on initiation using parameters from Vuex store

I am new to Vue and am trying to build a simple movie app, fetching data from an API and rendering the results. I want to have an incremental search feature. I have an input field in my navbar and when the user types, I want to redirect from the dashboard view to the search results view. I am unsure of how to pass the query params from the navbar to the search results view.
Here is my App.vue component
<template>
<div id="app">
<Navbar></Navbar>
<router-view/>
</div>
</template>
<script>
import Navbar from './components/Navbar.vue'
export default {
name: 'App',
components: {
Navbar
},
}
</script>
And here is my navbar component where I have the input field
<template>
<nav class="navbar">
<h1 class="logo" v-on:click="goToHome">Movie App</h1>
<input class="search-input" v-on:keyup="showResults" v-model="query" type="text" placeholder="Search..."/>
</nav>
</template>
<script>
import router from '../router/index'
export default {
data: function () {
return {
query: this.query
}
},
methods: {
goToHome () {
router.push({name: 'Dashboard'})
},
showResults () {
//here on each key press I want to narrow my results in the SearchedMovies component
}
}
}
</script>
If I use router.push to the SearchedMovies component then I am only able to pass the query as a parameter once. I thought about using Vuex to store the query and then access it from the SearchedMovies component, but surely there is a better way of doing it?
I also read about using $emit but since my parent contains all the routes, I'm not sure how to go about this.
You don't need to redirect user anywhere. I've made a small demo to show how one might do it. I used this navbar component as you described and emit an event from it:
const movies = {
data: [
{
id: 0,
title: 'Eraserhead',
},
{
id: 1,
title: 'Erazerhead',
},
{
id: 2,
title: 'Videodrome',
},
{
id: 3,
title: 'Videobrome',
},
{
id: 4,
title: 'Cube',
},
]
};
Vue.component('navbar', {
template: '<input v-model="filter" #input="onInput" placeholder="search">',
data() {
return {
filter: '',
};
},
methods: {
onInput() {
this.$emit('filter', this.filter);
}
}
});
// this is just a request imitation.
// just waiting for a second until we get a response
// from the datasample
function request(title) {
return new Promise((fulfill) => {
toReturn = movies.data.filter(movie => movie.title.toLowerCase().indexOf(title.toLowerCase()) !== -1)
setTimeout(() => fulfill(toReturn), 1000);
});
}
new Vue({
el: '#app',
data: {
movies: undefined,
loading: false,
filter: '',
lastValue: '',
},
methods: {
filterList(payload) {
// a timeout to prevent
// instant request on every input interaction
this.lastValue = payload;
setTimeout(() => this.makeRequest(), 1000);
},
makeRequest() {
if (this.loading) {
return;
}
this.loading = true;
request(this.lastValue).then((response) => {
this.movies = response;
this.loading = false;
});
}
},
mounted() {
this.makeRequest('');
}
})
<script src="https://unpkg.com/vue"></script>
<div id="app">
<navbar v-on:filter="filterList"></navbar>
<ul v-if="!loading">
<li v-for="movie in movies" :key="movie.id">{{ movie.title }}</li>
</ul>
<p v-else>Loading...</p>
</div>
Also jsfiddle: https://jsfiddle.net/oniondomes/rsyys3rp/
If you have any problem to understand the code above let me know.
EDIT: Fixed some bugs and added a couple of comments
EDIT2(after the comment below):
Here's what you can do. Every time user inputs something inside a navbar you call a function:
// template
<navbar v-on:input-inside-nav-bar="atInputInsideNavBar"></navbar>
// script
methods: {
atInputInsideNavBar(userInput) {
this.$router.push({
path: '/filtred-items',
params: {
value: userInput
}
})
}
}
Then inside you 'searched movies' page component you can access this value so:
this.$route.params.value // returns userInput from root component