I'm looking at upgrading to v3 and was disappointed to see inline-template has been removed. Therefore I'm trying to convert to use a scoped slot instead.
I have defined the following list component:
<template>
<slot :items="filteredItems" :page="page" :totalPages="totalPages" :onPageChanged="onPageChanged"></slot>
</template>
<script>
export default {
props: {
items: {
type: Array
},
initialPage: {
type: Number,
default: 1,
},
pageSize: {
type: Number,
default: 10
}
},
beforeCreate() {
this.page = this.initialPage;
},
computed: {
filteredItems() {
return this.items.slice((this.page - 1) * this.pageSize, this.page * this.pageSize);
},
totalPages() {
return Math.ceil(this.items.length / this.pageSize);
}
},
methods: {
onPageChanged(page) {
console.log('Page changed!!!');
this.page = page;
}
}
};
</script>
Which is called like so:
<list :items="[ { foo: 'A' }, { foo: 'B' }, { foo: 'C' } ]" :page-size="2" #="{ items, page, totalPages, onPageChanged }">
<ul class="list-group">
<li class="list-group-item" v-for="item in items">{{ item.foo }}</li>
</ul>
<pager :page="page" :total-pages="totalPages" #pageChanged="onPageChanged"></pager>
</list>
Here's the pager component:
<template>
<ul class="pagination">
<li class="page-item" v-if="hasPreviousPage">«</li>
<li class="page-item" v-if="hasPreviousPage">‹</li>
<li v-for="page in pages" :class="['page-item', { active: page.isActive }]">{{ page.name }}</li>
<li class="page-item disabled" v-if="page < totalPages - 2"><span class="page-link"> ... </span></li>
<li class="page-item" v-if="page < totalPages - 2">{{ totalPages }}</li>
<li class="page-item" v-if="hasNextPage">›</li>
<li class="page-item" v-if="hasNextPage">»</li>
</ul>
</template>
<script>
export default {
props: {
page: {
type: Number,
required: true
},
totalPages: {
type: Number,
required: true
}
},
computed: {
hasPreviousPage() {
return this.page > 1;
},
hasNextPage() {
return this.page < this.totalPages;
},
pages() {
const range = [];
for (let i = this.page <= 2 ? 1 : this.page - 2; i <= (this.page >= this.totalPages - 2 ? this.totalPages : this.page + 2); i++) {
range.push({
name: i,
isActive: this.page == i
});
}
return range;
}
},
methods: {
changePage(page, e = event) {
e.preventDefault();
// Trigger the page changed event.
this.$emit('pageChanged', page);
}
}
};
</script>
However whenever I try to change the page, the changePage method is invoked which emits the pageChanged event, but it doesn't invoke the onPageChanged method within the list component.
I'd appreciate if someone could show me what I'm doing wrong. Thanks
The event name should be written in kebab-case format as follows :
this.$emit('page-changed', page);
and use it like #page-changed="onPageChanged
Related
I'm trying to toggle between the 2 images by triggering a function on click event, as shown below, how do I apply the same idea to different list items to toggle the images individually for each item? right now, all the list items same image because of one global value i.e val
var app = new Vue({
el: '#app',
data: function() {
return {
val:true,
selectedImg:"",
checked: "#/assets/images/check.png",
unchecked: "#/assets/images/uncheck.png",
items: [
{ message: 'laundry' },
{ message: 'cooking' },
{ message: 'cleaning'}
]
}
},
methods: {
myfunc () {
this.val = (this.val === true ? false : true)
if(this.val === true) {
this.selectedImg = this.checked
} else {
this.selectedImg = this.unchecked
}
},
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<ul>
<li v-for="item in items" :key="item.message">
<button #click="myfunc"><img :src="selectedImg"/></button>
{{ item.message }}
</li>
</ul>
</div>
You should move selectedImg and val into items objects and pass item to myfunc function.
You can also look at the answer in codepen.
var app = new Vue({
el: '#app',
data: function() {
return {
checked: "#/assets/images/check.png",
unchecked: "#/assets/images/uncheck.png",
items: [
{ message: 'laundry', selectedImg:"" , val:true},
{ message: 'cooking', selectedImg:"", val:true},
{ message: 'cleaning', selectedImg:"", val:true}
]
}
},
methods: {
myfunc (item) {
item.val = (item.val === true ? false : true)
if(item.val === true) {
item.selectedImg = this.checked
} else {
item.selectedImg = this.unchecked
}
},
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<ul>
<li v-for="item in items" :key="item.message">
<button #click="myfunc(item)"><img :src="item.selectedImg"/></button>
{{ item.message }}
</li>
</ul>
</div>
import Vue from 'vue';
export default {
data() {
return {
cities : [
'Bangalore','Chennai','Cochin','Delhi','Kolkata','Mumbai'
],
value: '',
open: false,
current: 0
}
},
props: {
suggestions: {
type: Array,
required: true
},
selection: {
type: String,
required: true,
twoWay: true
}
},
computed: {
matches() {
return this.suggestions.filter((str) => {
return str.indexOf(this.selection) >= 0;
});
},
openSuggestion() {
return this.selection !== "" &&
this.matches.length != 0 &&
this.open === true;
}
},
methods: {
enter() {
this.selection = this.matches[this.current];
this.open = false;
},
up() {
if(this.current > 0)
this.current--;
},
down() {
if(this.current < this.matches.length - 1)
this.current++;
},
isActive(index) {
return index === this.current;
},
change() {
if (this.open == false) {
this.open = true;
this.current = 0;
}
},
suggestionClick(index) {
this.selection = this.matches[index];
this.open = false;
},
}
}
<template>
<div style="position:relative" v-bind:class="{'open':openSuggestion}">
<input class="form-control" type="text" v-model="selection"
#keydown.enter = 'enter'
#keydown.down = 'down'
#keydown.up = 'up'
#input = 'change'
/>
<ul class="dropdown-menu" style="width:100%">
<li
v-for="suggestion in matches"
v-bind:class="{'active': isActive($index)}"
#click="suggestionClick($index)"
>
{{ suggestion }}
</li>
</ul>
</div>
</template>
Getting eslint error [vue/require-v-for-key] Elements in iteration expect to have 'v-bind:key' directives.eslint-plugin-vue.
Tried changing to v-bind:key="suggestion.id" after changing, eslint error is not showing but issue is Autocomplete is not displaying(completely not working).
Can any one correct me if anything is wrong in the code.
When using v-for Vue would like to have a hint on how to identify the items in your list. You don't have to do it, but it is considered best practice and therefore eslint marks it.
To give the hint you add a key attribute with a unique value (id, some text, whatever) to the rendered list item like this:
<li
v-for="suggestion in matches"
v-bind:key="suggestion.id"
>
v-bind:key or :key in short. The value must be of type number | string | boolean | symbol.
See the docs for more info: https://v2.vuejs.org/v2/guide/list.html#Maintaining-State
As you already added the v-bind:key, Now in your code, I believe the issue is with $index, remove that and use the suggestions' index.
Here is the updated code, replace your dropdown-menu and check:
<ul class="dropdown-menu" style="width:100%">
<li
v-for="(suggestion, index) in matches"
v-bind:key="suggestion.id"
v-bind:class="{'active': isActive(index)}"
#click="suggestionClick(index)"
>
{{ suggestion }}
</li>
</ul>
I build a pagination component that works fine. But I don't know how to limit the shown items in the v-for loop and show the next items on the next page.
this is the pagination component:
<template>
<div>
<ul class="pagination">
<li class="pagination-item">
<button
type="button"
#click="onClickFirstPage"
:disabled="isInFirstPage"
>
First
</button>
</li>
<li class="pagination-item">
<button
type="button"
#click="onClickPreviousPage"
:disabled="isInFirstPage"
>
Previous
</button>
</li>
<li v-for="page in pages" :key="page.name" class="pagination-item">
<button #click="onClickPage(page.name)" type="button" :disabled="page.isDisabled" :class="{ active: isPageActive(page.name )}">
{{ page.name }}
</button>
</li>
<!-- Range of pages -->
<li>
<button
type="button"
#click="onClickNextPage"
:disabled="isInLastPage"
>
Next
</button>
</li>
<li>
<button
type="button"
#click="onClickLastPage"
:disabled="isInLastPage"
>
Last
</button>
</li>
</ul>
</div>
<script>
export default {
props: {
maxVisibleButtons: {
type: Number,
required: false,
default: 3
},
totalPages: {
type: Number,
required: true
},
total: {
type: Number,
required: true
},
currentPage: {
type: Number,
required: true
}
},
computed: {
startPage() {
// When on the first page
if(this.currentPage === 1) {
return 1;
}
// When on the last page
if(this.currentPage === this.totalPages) {
return this.totalPages - this.maxVisibleButtons;
}
// When in between
return this.currentPage - 1;
},
pages() {
const range = [];
for(let i = this.startPage; i <= Math.min(this.startPage + this.maxVisibleButtons - 1, this.totalPages); i+=1) {
range.push({
name: i,
isDisabled: i === this.currentPage
});
}
return range;
},
isInFirstPage() {
return this.currentPage === 1;
},
isInLastPage() {
return this.currentPage === this.totalPages;
},
},
methods: {
onClickFirstPage() {
this.$emit("pagechanged", 1);
},
onClickPreviousPage() {
this.$emit("pagechanged", this.currentPage - 1);
},
onClickPage(page) {
this.$emit('pagechanged', page);
},
onClickNextPage() {
this.$emit('pagechanged', this.currentPage + 1);
},
onClickLastPage() {
this.$emit('pagechanged', this.totalPages);
},
isPageActive(page) {
return this.currentPage === page;
}
}
}
</script>
<style scoped lang="scss">
.pagination {
list-style-type: none;
}
.pagination-item {
display: inline-block;
}
.active {
background-color: #4AAE9B;
color: #ffffff;
}
</style>
my v-for with the items
<div
v-for="casino in orderBy(filteredCasinos, 'Rating', -1)"
:key="casino.id + '-filterd'"
class="casino-card"
v-show="filteredCasinos.length > 1"
>
At the moment the pagination works when I click to the next page but as I mentioned all items get shown and I want to limit them.
Use a computed property to keep track what should be visible on the current page.
With a computed property you can easily introduce a filter on the items. Then just create the functions that sets the current page, and BAM - a paginated view of items.
The snippet below does all this, and also gives the ability to choose how many items should be visible on a given page.
Advice:
new Vue({
el: "#app",
data() {
return {
posts: [],
currentPage: 1,
postsPerPage: 10,
}
},
computed: {
// computed property to set the items visible on current page
currentPagePosts() {
return this.posts.slice((this.currentPage - 1) * this.postsPerPage, this.currentPage * this.postsPerPage)
}
},
methods: {
// pagination function
setCurrentPage(direction) {
if (direction === -1 && this.currentPage > 1) {
this.currentPage -= 1
} else if (direction === 1 && this.currentPage < this.posts.length / this.postsPerPage) {
this.currentPage += 1
}
}
},
// fetching example data (200 post-like items)
async mounted() {
const response = await fetch('https://jsonplaceholder.typicode.com/todos')
this.posts = await response.json()
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<button #click="setCurrentPage(-1)">PREV</button>
<button #click="setCurrentPage(1)">NEXT</button><br /> Current page / max page: {{ currentPage }} / {{ Math.ceil(posts.length / postsPerPage) }}<br />
<label>Set posts per page: <input v-model="postsPerPage" type="text" /></label>
<hr />
<!-- the v-for only lists items from the computed property -->
<div v-for="post in currentPagePosts" :key="post.id">
ID: {{ post.id }} Title: {{ post.title }}
</div>
</div>
I'm implementing Vue paper dashboard sidebar. So I have something like this:
Into Index I have
<template>
<div>
AdminIndex
<side-bar>
</side-bar>
</div>
</template>
<script>
import { faBox, faImages } from '#fortawesome/fontawesome-free-solid';
import Sidebar from '#/components/sidebar/SideBar';
export default {
name: 'admin-index-view',
components: {
SideBar,
},
data() {
return {
showSidebar: false,
sidebarLinks: [
{
name: 'admin.menu.products',
icon: faBoxes,
route: { name: 'adminProducts' },
},
{
name: 'admin.menu.sliders',
icon: faImages,
route: { name: '/admin/stats' },
},
],
};
},
methods: {
displaySidebar(value) {
this.showSidebar = value;
},
},
};
</script>
SideBar component:
<template>
<div :class="sidebarClasses"
:data-background-color="backgroundColor"
:data-active-color="activeColor">
<!--
Tip 1: you can change the color of the sidebar's background using: data-background-color="white | black | darkblue"
Tip 2: you can change the color of the active button using the data-active-color="primary | info | success | warning | danger"
-->
<!-- -->
<div class="sidebar-wrapper"
id="style-3">
<div class="logo">
<a href="#"
class="simple-text">
<div class="logo-img">
<img src="static/img/vue-logo.png"
alt="">
</div>
Paper Dashboard
</a>
</div>
<slot>
</slot>
<ul :class="navClasses">
<!--By default vue-router adds an active class to each route link. This way the links are colored when clicked-->
<router-link v-for="(link,index) in sidebarLinks"
:key="index"
:to="link.route"
tag="li"
:ref="link.name">
<a>
<font-awesome-icon :icon="link.icon" />
<p v-t="link.name" />
</a>
</router-link>
</ul>
<moving-arrow :move-y="arrowMovePx">
</moving-arrow>
</div>
</div>
</template>
<script>
import FontAwesomeIcon from '#fortawesome/vue-fontawesome';
import MovingArrow from './MovingArrow';
export default {
name: 'side-bar',
components: {
MovingArrow,
FontAwesomeIcon,
},
props: {
type: {
type: String,
default: 'sidebar',
validator: value => {
const acceptedValues = ['sidebar', 'navbar'];
return acceptedValues.indexOf(value) !== -1;
},
},
backgroundColor: {
type: String,
default: 'black',
validator: value => {
const acceptedValues = ['white', 'black', 'darkblue'];
return acceptedValues.indexOf(value) !== -1;
},
},
activeColor: {
type: String,
default: 'success',
validator: value => {
const acceptedValues = [
'primary',
'info',
'success',
'warning',
'danger',
];
return acceptedValues.indexOf(value) !== -1;
},
},
sidebarLinks: {
type: Array,
default: () => [],
},
},
data() {
return {
linkHeight: 60,
activeLinkIndex: 0,
windowWidth: 0,
isWindows: false,
hasAutoHeight: false,
};
},
computed: {
sidebarClasses() {
if (this.type === 'sidebar') {
return 'sidebar';
}
return 'collapse navbar-collapse off-canvas-sidebar';
},
navClasses() {
if (this.type === 'sidebar') {
return 'nav';
}
return 'nav navbar-nav';
},
/**
* Styles to animate the arrow near the current active sidebar link
* #returns {{transform: string}}
*/
arrowMovePx() {
return this.linkHeight * this.activeLinkIndex;
},
},
watch: {
$route() {
this.findActiveLink();
},
},
methods: {
findActiveLink() {
this.sidebarLinks.find((element, index) => {
const found = element.path === this.$route.path;
if (found) {
this.activeLinkIndex = index;
}
return found;
});
},
},
mounted() {
this.findActiveLink();
},
};
</script>
I dont receive any issues or vue errors, sidebar just don't display. In Chrome console just return empty: <side-bar data-v-66018f3c=""></side-bar> Someone knows why sidebar is not binded? What I need to do to get correctly implementation of it? Regards
Chrome console error:
[Vue warn]: Unknown custom element: - did you register the
component correctly? For recursive components, make sure to provide
the "name" option.
I`m using the Vue.js 2 and Laravel 5.3 to build a web.
When I click the ajaxbtn, the class do not bind in the variable, Any idea?
*Here is the html.
<div id="root" class="container">
<ajaxbtns>
<ajaxbtn name="get taiwanstay" url="api/taiwanstay" :selected="true" ></ajaxbtn>
<ajaxbtn name="get itwyp" url="api/itwyp" ></ajaxbtn>
</ajaxbtns>
</div>
*Here is the script
Vue.component('ajaxbtns',{
template:
`
<div class="tabs">
<ul>
<slot></slot>
</ul>
</div>
`,
data : function () {
return {
allAjaxBtn : []
};
},
created: function () {
this.allAjaxBtn = this.$children;
}
});
Vue.component('ajaxbtn',{
template:
`
<li :class="{ 'is-active' : btnActive }">
<a #click="ajaxbtnClick(name)" href="#" >#{{ name }}</a>
</li>
`,
props : {
name: { required: true },
url : { required: true },
selected: { default : false }
},
data :function () {
return {
btnActive : false
}
},
created: function () {
this.btnActive = this.selected;
},
methods : {
ajaxbtnClick : function (name) {
this.$parent.allAjaxBtn.forEach( btn => {
this.btnActive = (btn.name == name);
});
}
}
});
new Vue({
el: '#root'
});
The issue is not with the binding of the variable, that works fine. The problem is that btnActive will change for each iteration. You may get lucky in the case that the last btn matches, which would set it to true. However, if the condition was met earlier, it would be switched from true to false anyway.
Instead, conditionalize your query and then assign the btn to the btnActive:
if (btn.name == name) {
this.btnActive = btn;
}