BootstrapVue's b-paginate component renders but doesn't function - vuejs2

Please excuse my ignorance as a new web developer, but I can't seem to get the <b-paginate> component to work in my single file Django and Vue 2 app. I access Bootstrap Vue via CDN.
This is my component, which is placed directly above my main Vue app:
let paginationComponent = Vue.component('pagination-component', {
name: 'paginationComponent',
props:['pantryItems'],
template:`<div class="overflow-auto"><b-pagination
v-model="currentPage"
:total-rows="rows"
:per-page="perPage"
first-text="First"
prev-text="Prev"
next-text="Next"
last-text="Last"
class="mt-4"
></b-pagination></div>`,
data: function() {
return {
rows: this.pantryItems.length,
perPage: 10,
currentPage: 1,
}
},
computed: {
rows: function() {
return this.pantryItems.length
}
}
})
and this is my Vue root app:
let mypantryapp = new Vue ({
el: "#app",
delimiters: ['[[', ']]'],
components: {'pagination-component': paginationComponent},
data: {
pantryItems: [],
name: '',
users: [],
itemName: '',
createdDate: '',
expirationDate: '',
csrf_token: '',
itemErrors: {currentUser: false},
currentUser: {id: false},
owner: '',
itemImgs: [],
tags: [],
by_category: false,
grocery_view: false,
...followed by a bunch of unrelated methods for my pantry inventory app.
This is how I call it in my HTML:
<b-pagination :pantry-items='pantryItems' ></b-pagination>
The component renders on the page with just the number 1 in the middle of the pagination button group and all other buttons greyed out.

The only mistake you've made here is confusing b-pagination for your own pagination-component child component.
You're invoking b-pagination perfectly in your template option, you just need to add the child component itself into your HTML to actually use it. The template parameter will replace the HTML of your child component in the DOM.
Simply change:
<b-pagination :pantry-items='pantryItems'></b-pagination>
to:
<pagination-component :pantry-items='pantryItems'></pagination-component>
And it should work correctly! Example below:
let paginationComponent = Vue.component('pagination-component', {
name: 'paginationComponent',
props: ['pantryItems'],
template: `<div class="overflow-auto"><b-pagination
v-model="currentPage"
:total-rows="rows"
:per-page="perPage"
first-text="First"
prev-text="Prev"
next-text="Next"
last-text="Last"
class="mt-4"
></b-pagination></div>`,
data: function() {
return {
//rows: this.pantryItems.length, *** See note below
perPage: 1, //Lower perPage for example sake
currentPage: 1,
}
},
computed: {
rows: function() {
return this.pantryItems.length
}
}
});
let mypantryapp = new Vue({
el: "#app",
delimiters: ['[[', ']]'],
components: {
'pagination-component': paginationComponent
},
data: {
pantryItems: [1, 2, 3, 4, 5], //Dummy items for example sake
}
});
<!-- Set up Vue and Bootstrap-Vue for snippet -->
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap#4/dist/css/bootstrap.min.css" /><link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.min.css" /><script src="//unpkg.com/vue#latest/dist/vue.min.js"></script><script src="//unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.min.js"></script>
<div id="app">
<pagination-component :pantry-items='pantryItems'></pagination-component>
</div>
As one final note, you need only declare rows once. In this case, you should remove it from data, because it will change if your total items changes. Your computed rows will reflect the change, the rows var in data will not (since it will be set once at startup and then never again).

Related

How is the method Total being called in this example

I see in the code below how the list item's class and state is being modified but I don't understand where or how the total() method is being triggered. The total is added to the markup in the <span>{{total() | currency}}</span> but there is no click event or anything reactive that I see in the code that is bound to it.
<template>
<!-- v-cloak hides any un-compiled data bindings until the Vue instance is ready. -->
<form id="main" v-cloak>
<h1>Services</h1>
<ul>
<!-- Loop through the services array, assign a click handler, and set or
remove the "active" css class if needed -->
<li
v-for="service in services"
v-bind:key="service.id"
v-on:click="toggleActive(service)"
v-bind:class="{ 'active': service.active}">
<!-- Display the name and price for every entry in the array .
Vue.js has a built in currency filter for formatting the price -->
{{service.name}} <span>{{service.price | currency}}</span>
</li>
</ul>
<div class="total">
<!-- Calculate the total price of all chosen services. Format it as currency. -->
Total: <span>{{total() | currency}}</span>
</div>
</form>
</template>
<script>
export default {
name: 'OrderForm',
data(){
return{
// Define the model properties. The view will loop
// through the services array and genreate a li
// element for every one of its items.
services: [
{
name: 'Web Development',
price: 300,
active:true
},{
name: 'Design',
price: 400,
active:false
},{
name: 'Integration',
price: 250,
active:false
},{
name: 'Training',
price: 220,
active:false
}
]
}
},
// Functions we will be using.
methods: {
toggleActive: function(s){
s.active = !s.active;
},
total: function(){
var total = 0;
this.services.forEach(function(s){
if (s.active){
total+= s.price;
}
});
return total;
}
},
filters: {
currency: function(value) {
return '$' + value.toFixed(2);
}
}
}
</script>
EDIT:
Working example https://tutorialzine.com/2016/03/5-practical-examples-for-learning-vue-js
So I believe the explanation for what is happening is that data's services object is reactive. Since the total method is being bound to it, when the toggleActive method is being called, it is updating services which causes the total method to also be called.
From the docs here 'How Changes Are Tracked' https://v2.vuejs.org/v2/guide/reactivity.html
Every component instance has a corresponding watcher instance, which records any properties “touched” during the component’s render as dependencies. Later on when a dependency’s setter is triggered, it notifies the watcher, which in turn causes the component to re-render.
Often I find simplifying what is going on helps me understand it. If you did a very simplified version of above it might look like this.
<div id="app">
<button #click="increment">Increment by 1</button>
<p>{{total()}}</p>
</div>
new Vue({
el: "#app",
data: {
counter: 0,
},
methods: {
increment: function(){
this.counter += 1;
},
total: function(){
return this.counter;
}
}
})
working example: https://jsfiddle.net/skribe/yq4moz2e/10/
If you simplify it even further by putting the data property counter in the template, when its value changes, you would naturally expect the value in the template to also be updated. So this should help you understand why the total method gets called.
<div id="app">
<button #click="increment">Increment by 1</button>
<p>{{counter}}</p>
</div>
new Vue({
el: "#app",
data: {
counter: 0,
},
methods: {
increment: function(){
this.counter += 1;
},
}
})
working example: https://jsfiddle.net/skribe/yq4moz2e/6/
When you update the data, the template in the components rerendered. That means that the template will trigger all methods bind to the templates. You can see it by adding dynamic date for example.
<div id="app">
<button #click="increment">Increment by 1</button>
<p>{{total()}}</p>
<p>
// Date will be updated after clicking on increment:
{{date()}}
</p>
</div>
new Vue({
el: "#app",
data: {
counter: 0,
},
methods: {
increment: function(){
this.counter += 1;
},
total: function(){
return this.counter;
},
date: function() {
return new Date();
}
}
})

Vuejs component doesn't update when changing variable

I'm learning Vue.js and have been able to write a simple list/detail application. Selecting the first item renders the detail component with the correct data, however when I select a different item the detail component doesn't reload with the right information.
For example:
<template>
<div>
<detail :obj="currentObject"></detail>
</div>
</template>
<script>
export default: {
data: function(){
return {
currentObject: null,
objs = [
{name:'obj 1'},
{name:'obj 2'}
]
};
}
}
</script>
When I do this.currentObject = objs[0] the component detail updates with the correct content. However the next time I call this.currentObject = objs[1], the component detail no longer updates.
Not sure what's the context your are switching the data on your currentObject, but the below is a concept detail component and when you switch the objs it updated the prop :obj and seems working fine.
Looking at your code, you should declare objs using : not =.
data: function() {
return {
currentObject: null,
objs: [
{name:'obj 1'},
{name:'obj 2'}
]
};
}
Here is the concept detail component, run the snippet to check it working.
Vue.component('detail', {
props: ['obj'],
template: '<div>{{ obj }}</div>'
})
var app = new Vue({
el: '#app',
data() {
return {
bToggle: false,
currentObject: null,
objs: [
{name:'obj 1'},
{name:'obj 2'}
]
}
},
created(){
this.switchData();
},
methods: {
switchData() {
if(!this.bToggle){
this.currentObject = this.objs[0];
}else{
this.currentObject = this.objs[1];
}
this.bToggle = !this.bToggle;
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.17/dist/vue.js"></script>
<div id="app">
<button #click="switchData()"> switch </button>
<detail :obj="currentObject"></detail>
</div>

Vue. How to get a value of a "key" attribute in created element

I am trying to create a component and get its :key for using in axios.
The element is created, but I can't get a key. It's undefined.
<div class="container" id="root">
<paddock is="paddock-item" v-for="paddock in paddocks" :key="paddock.key" class="paddock">
</paddock>
</div>
<script>
var pItem = {
props: ['key'],
template: '<div :test="key"></div>',
created: function() {
console.log(key);
}
};
new Vue({
el: '#root',
components: {
'paddock-item': pItem
},
data: {
paddocks: [
{key: 1},
{key: 2},
{key: 3},
{key: 4}
]
}
})
</script>
I tried some variants, but no result. key was empty.
you don't need to use an extra attribute. You can get the key by
this.$vnode.key
In Vue 3 you can get the key with:
import { getCurrentInstance} from "vue";
getCurrentInstance().vnode.key
This answer answers the question of how you would pass the key to a child component. If you just want to get the current key from inside the child component, use the highest voted answer.
`key` is a [special attribute][1] in Vue. You will have to call your property something else.
Here is an alternative using pkey instead.
console.clear()
var pItem = {
props: ['pkey'],
template: '<div :test="pkey"></div>',
created: function() {
console.log(this.pkey);
}
};
new Vue({
el: '#root',
components: {
'paddock-item': pItem
},
data: {
paddocks: [{
key: 1
},
{
key: 2
},
{
key: 3
},
{
key: 4
}
]
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.js"></script>
<div class="container" id="root">
<paddock-item v-for="paddock in paddocks" :pkey="paddock.key" :key="paddock.key" class="paddock">
</paddock-item>
</div>
In Vue 3 you can get the key by
this.$.vnode.key
Using this.$options._parentVnode.key work for me:
created() {
console.log(this.$options._parentVnode.key)
}
Using Vue extension for chrome you can see the keys in the elements tree:
Here is the link to extension:
https://chrome.google.com/webstore/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd

In vue2 v-for nested component props aren't updated after element is removed in parent

For my app I'm using two Vue components. One that renders a list of "days" and one that renders for each "day" the list of "locations". So for example "day 1" can have the locations "Berlin", "London", "New York".
Everything gets rendered ok but after removing the "Day 1" from the list of days the view isn't rendered corrected. This is what happens:
The title of the day that was removed is replaced -> Correct
The content of the day that was removed isn't replaced -> Not correct
Vue.component('day-list', {
props: ['days'],
template: '<div><div v-for="(day, index) in dayItems">{{ day.name }} Remove day<location-list :locations="day.locations"></location-list><br/></div></div>',
data: function() {
return {
dayItems: this.days
}
},
methods: {
remove(index) {
this.dayItems.splice(index, 1);
}
}
});
Vue.component('location-list', {
props: ['locations', 'services'],
template: '<div><div v-for="(location, index) in locationItems">{{ location.name }} <a href="#" #click.prevent="remove(index)"</div></div>',
data: function() {
return {
locationItems: this.locations
}
},
methods: {
remove(index) {
this.locationItems.splice(index, 1);
}
}
});
const app = window.app = new Vue({
el: '#app',
data: function() {
return {
days: [
{
name: 'Day 1',
locations: [
{name: 'Berlin'},
{name: 'London'},
{name: 'New York'}
]
},
{
name: 'Day 2',
locations: [
{name: 'Moscow'},
{name: 'Seul'},
{name: 'Paris'}
]
}
]
}
},
methods: {}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.3/vue.js"></script>
<div id="app">
<day-list :days="days"></day-list>
</div>
Please use Vue-devtools if you are not already using it. It shows the problem clearly, as seen in the image below:
As you can see above, your day-list component comprises of all the days you have in the original list, with locations listed out directly. You need one more component in between, call it day-details, which will render the info for a particular day. You may have the location-list inside the day-details.
Here is the updated code which works:
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.3/vue.js"></script>
<div id="app">
<day-list :days="days"></day-list>
</div>
Vue.component('day-list', {
props: ['days'],
template: `
<div>
<day-details :day="day" v-for="(day, index) in days">
Remove day
</day-details>
</div>`,
methods: {
remove(index) {
this.days.splice(index, 1);
}
}
});
Vue.component('day-details', {
props: ['day'],
template: `
<div>
{{ day.name }}
<slot></slot>
<location-list :locations="day.locations"></location-list>
<br/>
</div>`
});
Vue.component('location-list', {
props: ['locations', 'services'],
template: `
<div>
<div v-for="(location, index) in locations">
{{ location.name }}
[x]
</div>
</div>
`,
methods: {
remove(index) {
this.locations.splice(index, 1);
}
}
});
const app = window.app = new Vue({
el: '#app',
data: function() {
return {
days: [{
name: 'Day 1',
locations: [{
name: 'Berlin'
}, {
name: 'London'
}, {
name: 'New York'
}]
}, {
name: 'Day 2',
locations: [{
name: 'Moscow'
}, {
name: 'Seul'
}, {
name: 'Paris'
}]
}]
}
},
methods: {}
});
One other thing - your template for location-list has an error - you are not closing the <a> element. You may use backtick operator to have multi-line templates as seen in the example above, to avoid template errors.
Also you are not supposed to change objects that are passed via props. It works here because you are passing objects which are passed by reference. But a string object getting modified in child component will result in this error:
[Vue warn]: Avoid mutating a prop directly...
If you ever get this error, you may use event mechanism as explained in the answer for this question: Delete a Vue child component

Vue.js directive within a template

Based on this example https://vuejs.org/examples/select2.html
I try to create component for reuse in a future.
Unfortunately, my code doesn't work.
HTML:
<template id="my-template">
<p>Selected: {{selected}}</p>
<select v-select="selected" :options="options">
<option value="0">default</option>
</select>
</template>
<div id="app">
<my-component></my-component>
</div>
Vue:
Vue.component('my-component', {
template: '#my-template'
})
Vue.directive('select', {
twoWay: true,
priority: 1000,
params: ['options'],
bind: function () {
var self = this
$(this.el)
.select2({
data: this.params.options
})
.on('change', function () {
self.set(this.value)
})
},
update: function (value) {
$(this.el).val(value).trigger('change')
},
unbind: function () {
$(this.el).off().select2('destroy')
}
})
var vm = new Vue({
el: '#app',
data: {
selected: 0,
options: [
{ id: 1, text: 'hello' },
{ id: 2, text: 'what' }
]
}
})
https://jsfiddle.net/xz62be63/
Thank you for any help!
your problem has nothing to do with the directive itself.
selected and options is defined in the data of your Vue main instance, not in the data of my-component - so it's not available in its template.
But you can pass it from the main instance to the component using props:
<div id="app">
<my-component :selected.sync="selected" :options="options"></my-component>
<!-- added the .sync modifier to transfer the value change back up to the main instance-->
</div>
and in the code:
Vue.component('my-component', {
template: '#my-template',
props: ['selected','options']
})
updated fiddle: https://jsfiddle.net/Linusborg/rj1kLLuc/