vuejs2 event listening not working - vue.js

I am following this tutorial https://github.com/ratiw/vuetable-2-tutorial and trying to develop my laravel admin panel using vuejs.
Filter in the list vue is not working
This is my list.vue
<template>
<div>
<filter-bar></filter-bar>
<vuetable ref="vuetable"
api-url="/api/category/list"
:fields="fields"
pagination-path=""
:css="css.table"
:sort-order="sortOrder"
:multi-sort="true"
detail-row-component="my-detail-row"
:append-params="moreParams"
#vuetable:cell-clicked="onCellClicked"
#vuetable:pagination-data="onPaginationData"
></vuetable>
<div class="vuetable-pagination">
<vuetable-pagination-info ref="paginationInfo"
info-class="pagination-info"
></vuetable-pagination-info>
<vuetable-pagination ref="pagination"
:css="css.pagination"
:icons="css.icons"
#vuetable-pagination:change-page="onChangePage"
></vuetable-pagination>
</div>
</div>
</template>
<script>
import VueEvents from 'vue-events'
Vue.use(VueEvents)
var Vuetable = require('vuetable-2/src/components/Vuetable.vue');
Vue.use(Vuetable);
var VuetablePagination = require('vuetable-2/src/components/VuetablePagination.vue');
var VuetablePaginationInfo = require('vuetable-2/src/components/VuetablePaginationInfo.vue');
Vue.component('custom-actions', require('../CustomActions.vue'))
Vue.component('filter-bar', require('../FilterBar.vue'))
export default {
components: {
Vuetable,
VuetablePagination,
VuetablePaginationInfo,
},
data () {
return {
fields: [
{
name: 'id',
sortField: 'id',
},
{
name: 'name',
sortField: 'name',
},
{
name: '__component:custom-actions',
title: 'Actions',
titleClass: 'text-center',
dataClass: 'text-center'
}
],
css: {
table: {
tableClass: 'table table-bordered table-striped table-hover',
ascendingIcon: 'glyphicon glyphicon-chevron-up',
descendingIcon: 'glyphicon glyphicon-chevron-down'
},
pagination: {
wrapperClass: 'pagination',
activeClass: 'active',
disabledClass: 'disabled',
pageClass: 'page',
linkClass: 'link',
},
icons: {
first: 'glyphicon glyphicon-step-backward',
prev: 'glyphicon glyphicon-chevron-left',
next: 'glyphicon glyphicon-chevron-right',
last: 'glyphicon glyphicon-step-forward',
},
},
sortOrder: [
{ field: 'id', sortField: 'id', direction: 'asc'}
],
moreParams: {}
}
},
methods: {
onPaginationData (paginationData) {
this.$refs.pagination.setPaginationData(paginationData)
this.$refs.paginationInfo.setPaginationData(paginationData)
},
onChangePage (page) {
this.$refs.vuetable.changePage(page)
},
onCellClicked (data, field, event) {
console.log('cellClicked: ', field.name)
this.$refs.vuetable.toggleDetailRow(data.id)
},
onFilterSet (filterText) {
this.moreParams = {
'filter': filterText.trim()
}
Vue.nextTick( () => this.$refs.vuetable.refresh())
},
onFilterReset () {
this.moreParams = {}
Vue.nextTick( () => this.$refs.vuetable.refresh())
}
},
mounted() {
this.$events.$on('filter-set', eventData => this.onFilterSet(eventData))
this.$events.$on('filter-reset', e => this.onFilterReset())
}
}
</script>
<style>
.pagination {
margin: 0;
float: right;
}
.pagination a.page {
border: 1px solid lightgray;
border-radius: 3px;
padding: 5px 10px;
margin-right: 2px;
}
.pagination a.page.active {
color: white;
background-color: #337ab7;
border: 1px solid lightgray;
border-radius: 3px;
padding: 5px 10px;
margin-right: 2px;
}
.pagination a.btn-nav {
border: 1px solid lightgray;
border-radius: 3px;
padding: 5px 7px;
margin-right: 2px;
}
.pagination a.btn-nav.disabled {
color: lightgray;
border: 1px solid lightgray;
border-radius: 3px;
padding: 5px 7px;
margin-right: 2px;
cursor: not-allowed;
}
.pagination-info {
float: left;
}
</style>
Filter in the list vue is not working
This is my FilterBar.vue
<template>
<div class="filter-bar ui basic segment grid">
<div class="ui form">
<div class="inline field">
<label>Search for:</label>
<input type="text" v-model="filterText" class="three wide column" #keyup.enter="doFilter" placeholder="name, nickname, or email">
<button class="ui primary button" #click="doFilter">Go</button>
<button class="ui button" #click="resetFilter">Reset</button>
</div>
</div>
</div>
</template>
<script>
export default {
data () {
return {
filterText: ''
}
},
methods: {
doFilter () {
this.$events.fire('filter-set', this.filterText)
},
resetFilter () {
this.filterText = ''
this.$events.fire('filter-reset')
}
}
}
</script>
Event is being fired in the filterbar vue component, but listener in list.vue component doesn't pick it up
I tried the fix in the issues tab
1.using method and mounted
2.using created

Related

Vue 3 cli-service app: "Slot "default" invoked outside of the render function" warning when component with slots is imported from other component

MCVE
I have a Tabpane component that takes slots as input. When imported from the template it works as expected.
<Tabpane>
<div caption="I am div 1">Div 1</div>
<div caption="I am div 2">Div 2</div>
</Tabpane>
However when imported from an other component ( Composite in the example ), then it triggers the following warning:
Slot "default" invoked outside of the render function:
this will not track dependencies used in the slot. Invoke the slot function inside the render function instead.
// src/components/Composite.js
import { defineComponent, h } from "vue";
import Tabpane from "./Tabpane.vue";
export default defineComponent({
name: "Composite",
setup() {
const slots = [
h("div", { caption: "I am div 1" }, ["Div 1"]),
h("div", { caption: "I am div 2" }, ["Div 2"])
];
return () => h(Tabpane, {}, () => slots);
}
});
Solved.
The problem was that I called slots.default() from within setup, but not within the returned render function.
Also this component reflected a very beginner approach to reactivity. By now I know better. The old problematic solution is still there in src/components/Tabpane.vue.
The right solution that triggers no warning is:
// src/components/Tabpane2.vue
<script>
import { defineComponent, h, reactive } from "vue";
export default defineComponent({
name: "Tabpane2",
props: {
width: {
type: Number,
default: 400,
},
height: {
type: Number,
default: 200,
},
},
setup(props, { slots }) {
const react = reactive({
selectedTab: 0,
});
return () =>
h("div", { class: ["vertcont"] }, [
h(
"div",
{
class: ["tabs"],
},
slots.default().map((tab, i) =>
h(
"div",
{
class: {
tab: true,
selected: i === react.selectedTab,
},
onClick: () => {
react.selectedTab = i;
},
},
[tab.props.caption]
)
)
),
h(
"div",
{
class: ["slotscont"],
style: {
width: `${props.width}px`,
height: `${props.height}px`,
},
},
slots.default().map((slot, i) =>
h(
"div",
{
class: {
slot: true,
active: react.selectedTab === i,
},
},
[slot]
)
)
),
]);
},
});
</script>
<style>
.tab.selected {
background-color: #efe;
border: solid 2px #afa !important;
border-bottom: transparent !important;
}
.tab {
background-color: #eee;
}
.tabs .tab {
padding: 5px;
margin: 2px;
border: solid 2px #aaa;
border-radius: 8px;
border-bottom: transparent;
cursor: pointer;
user-select: none;
transition: all 0.5s;
color: #007;
}
.tabs {
display: flex;
align-items: center;
margin-left: 5px;
}
.vertcont {
display: flex;
flex-direction: column;
margin: 3px;
}
.slotscont {
position: relative;
overflow: scroll;
padding: 5px;
border: solid 1px #777;
}
.slot {
visibility: hidden;
position: absolute;
}
.slot.active {
visibility: visible;
}
</style>
Slots need to be invoked within the render function and or the <template> box to ensure they keep their reactivity.
A full explanation can be found in this post: https://zelig880.com/how-to-fix-slot-invoked-outside-of-the-render-function-in-vue-3

How to animate todo moving from one list to another with Vue.js?

I am trying to do this svelte example todo moving animation with Vue.js.
Below you can find what I have done so far. Just click on the todo to see.
new Vue({
el: "#app",
data: {
items: [
{ id: 1, name: 'John', done: false },
{ id: 2, name: 'Jane', done: false },
{ id: 3, name: 'Jade', done: true },
{ id: 4, name: 'George', done: true },
]
},
computed: {
done () {
return this.items.filter(i => i.done)
},
undone () {
return this.items.filter(i => !i.done)
}
},
methods: {
toggle: function(todo){
todo.done = !todo.done
}
}
})
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
height: 500px;
transition: all 0.2s;
}
.todos {
display: grid;
grid-template-columns: 1fr 1fr;
}
.todo {
border: 1px solid #ccc;
}
.todo.undone {
grid-column: 2 /span 1;
}
.todo.done {
grid-column: 1 /span 1;
background: blue;
color: white;
}
.flip-list-move {
transition: all 1s ease-in-out;
}
.header-wrapper {
display: grid;
grid-auto-flow: column;
}
.header, .todo {
display: grid;
grid-template-columns: repeat(3, 1fr);
padding: 5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div class="header-wrapper">
<div class="header">
<span>Name</span>
<span>Age</span>
<span>Gender</span>
</div>
<div class="header">
<span>Name</span>
<span>Age</span>
<span>Gender</span>
</div>
</div>
<transition-group name="flip-list" tag="div" class="todos">
<div class="todo done" v-for="item of done" :key="item.id" #click="toggle(item)">
<span>{{item.name}}</span>
<span>26</span>
<span>Male</span>
</div>
<div class="todo undone" v-for="item of undone" :key="item.id" #click="toggle(item)">
<span>{{item.name}}</span>
<span>20</span>
<span>Male</span>
</div>
</transition-group>
</div>
In order to animate the todo move from one list to another, I used CSS grid but I can't find a way to distinguish todos (left and right) without having a grid cell which is empty.
I would appreciate if there is a better way to achieve the example in svelte docs or a way to omit the empty cells.
Even though it seemed easy in the beginning, it's a bit tricky.
You can target the first element by tracking the index in the v-for loop. Index 0 is always going to be the first element. And give it the following style:
grid-row-start: 1;
EDIT DEMO:
new Vue({
el: "#app",
data: {
items: [
{ id: 1, name: 'John', done: false },
{ id: 2, name: 'Jane', done: false },
{ id: 3, name: 'Jade', done: true },
{ id: 4, name: 'George', done: true },
]
},
computed: {
done () {
return this.items.filter(i => i.done)
},
undone () {
return this.items.filter(i => !i.done)
}
},
methods: {
toggle: function(todo){
todo.done = !todo.done
}
}
})
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
height: 500px;
transition: all 0.2s;
}
.todos {
display: grid;
grid-template-columns: 1fr 1fr;
}
.todo {
border: 1px solid #ccc;
}
.todo.undone {
grid-column: 2 /span 1;
}
.todo.done {
grid-column: 1 /span 1;
background: blue;
color: white;
}
.first-right {
grid-row-start: 1;
}
.flip-list-move {
transition: all 1s ease-in-out;
}
.header-wrapper {
display: grid;
grid-auto-flow: column;
}
.header, .todo {
display: grid;
grid-template-columns: repeat(3, 1fr);
padding: 5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div class="header-wrapper">
<div class="header">
<span>Name</span>
<span>Age</span>
<span>Gender</span>
</div>
<div class="header">
<span>Name</span>
<span>Age</span>
<span>Gender</span>
</div>
</div>
<transition-group name="flip-list" tag="div" class="todos">
<div class="todo done" v-for="item of done" :key="item.id" #click="toggle(item)">
<span>{{item.name}}</span>
<span>26</span>
<span>Male</span>
</div>
<div :class="['todo', 'undone', { 'first-right': index === 0 }]" v-for="(item, index) of undone" :key="item.id" #click="toggle(item)">
<span>{{item.name}}</span>
<span>20</span>
<span>Male</span>
</div>
</transition-group>
</div>
Adding grid-row-start to the first undone element doesn't works if there are more than 6 items in array.
As a solution, I used the index of v-for loop to add to every undone todo the corresponding grid-row-start.
index starts at 0 so we have to make index + 1
<div
class="todo undone"
v-for="(item, index) of undone"
:key="item.id"
:style="{'grid-row': index + 1}" // => HERE we guarantee no gaps are present in undone list`
#click="toggle(item)"
>
<span>{{item.name}}</span>
<span>20</span>
<span>Male</span>
</div>
You can find the working example on this codesandbox

Nuxt / Vuex / Vue Reactivity Issue Increment

Hi everyone I am I having some difficulty when working with Nuxt and Vuex.
I am trying to run through the example Vuex / Nuxt Classic Mode.
https://nuxtjs.org/guide/vuex-store/
After clicking my increment button I dont see the number go up. My page just stays at 0, I can see within the console that the state knows the number is no longer 0 but not on the screen, as if it doesnt know to be reactive.
My assumption is that I have miss configured something somewhere and my 0 is not the actual state, but I created some copy of it somehow.
Here is my button within my template.
<button #click="inc">{{ counter }}</button>
Here is my inc function within my methods.
inc () {
this.$store.commit('increment')
},
Here is my computed
computed: {
counter () {
return this.$store.getters.counter
}
}
Here is my Vuex/index.js file contained within the store folder.
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const createStore = () => {
return new Vuex.Store({
state: () => ({
counter: 0
}),
getters: {
counter: state => state.counter
},
mutations: {
increment (state) {
state.counter++
}
}
})
}
export default createStore
Update: Included more code snippets, and replied to existing comments.
#Boussadjra Brahim #xaviert, thank you both for weighing in, I appreciate the assistance.
#Boussadjra Brahim - Yes, I had tried using an action that called the mutation, that didnt seem to get me there either. I also tried adjusting the state via the action alone, and wasnt able to make any changes, however that seems correct, as I am under the impression that actions call mutations to make state changes and do not themselves do so, please correct me if you know more. I am 100% open to the idea that I did not attempt it correctly. Below is that action that didnt do anything and the one that called the mutation
actions: {
increment (state) {
state.counter++
}
},
And here is the version with the action calling the mutation.
actions: {
incrementCounterUp () {
this.commit('increment')
}
},
#xaviert - I have tried starting the server over, and have also tried to see if an nuxt build followed by a firebase serve, to see if maybe that helped. It did not. My normal server start is 'npm run dev'. In hopes that you/anyone else may be able to find my mistake below is my full _id.vue component and also my nuxt.config.js file as maybe that's it. Its still pretty raw and could use a lot of refactoring so hope you can sort through it well enough.
_.id.vue
<template>
<div class="product">
<div class="product-image">
<div class="product-image-img">
<img v-bind:src="product.image_file" width="450px;"/>
</div>
</div>
<div class="product-details-wrapper">
<div class="product-details">
<h1>
<div v-if="this.$route.query.editPage">
<textarea #input="updateTextArea" ref="textarea2" v-model="product.item_name" type="text" />
</div>
<div v-else>{{product.item_name}}</div>
</h1>
<div class="product-description">
<div class="product-description-text" v-if="this.$route.query.editPage">
<textarea #input="updateTextArea" ref="textarea" v-model="product.description" type="text" />
</div>
<div class="product-description-text" v-else v-html="product.description"></div>
</div>
<p class="product-brand"><strong>Brand - </strong> {{product.brand_name}}</p>
<hr />
<div class="product-price">
<div v-if="this.$route.query.editPage">
<strong>Original Price - </strong>
<input v-model="product.msrp" type="text" />
</div>
<div v-else class="product-msrp">
<strong>Original Price - </strong>
<span class="strike">${{product.msrp}}</span>
</div>
<div v-if="this.$route.query.editPage">
<strong>Sale Price - </strong>
<input v-model="product.price" type="text" />
</div>
<div v-else class="product-sale-price">
<strong>Sale Price - </strong>
<span class="">${{product.price}}</span>
</div>
<div class="product-price">
Quantity x
<input #input="updateQuantity" v-model="quantity" min="1" class="" type="number" value="1" />
</div>
<button #click="inc">{{ counter }}</button>
</div>
</div>
</div>
<div v-if="this.$route.query.editPage" class="update-product"> <button #click="updateProduct(product)">Update</button></div>
<div class="footer">
<router-link to="/privacy-policy" target="_blank">Privacy</router-link> |
<router-link to="/terms" target="_blank">Terms</router-link>
</div>
</div>
</template>
<script>
// # is an alias to /src
import firebase from '#/services/fireinit'
import foo from '#/components/foo'
const db = firebase.firestore()
export default {
name: 'ProductPage',
head () {
return {
title: this.product.item_name
}
},
components: {
foo
},
data: function () {
return {
product: {},
image: '',
name: 'Checkout',
description: '',
currency: 'USD',
amount: '',
msrp: '',
quantity: 1
}
},
methods: {
inc () {
this.$store.dispatch('incrementCounterUp', true)
},
updateProduct: function (product) {
db.collection('products').doc(product.item_id).set(product)
.then(function () {
console.log('Document successfully written!')
})
.catch(function (error) {
console.error('Error writing document: ', error)
})
},
updateQuantity () {
this.product.msrp = (this.quantity * this.product.orgMsrp)
this.product.msrp = Math.round(100 * this.product.msrp) / 100
this.product.price = this.quantity * this.product.orgPrice
this.product.price = Math.round(100 * this.product.price) / 100
},
updateTextArea () {
this.$refs.textarea.style.minHeight = this.$refs.textarea.scrollHeight + 'px'
this.$refs.textarea2.style.minHeight = this.$refs.textarea2.scrollHeight + 'px'
}
},
async asyncData({app, params, error}) {
const ref = db.collection("products").doc(params.id)
let snap
let thisProduct = {}
try {
snap = await ref.get()
thisProduct = snap.data()
thisProduct.orgMsrp = thisProduct.msrp
thisProduct.orgPrice = thisProduct.price
} catch (e) {
// TODO: error handling
console.error(e)
}
return {
product: thisProduct
}
},
mounted () {
if(this.$refs.textarea) {
this.$refs.textarea.style.minHeight = this.$refs.textarea.scrollHeight + 'px'
this.$refs.textarea2.style.minHeight = this.$refs.textarea2.scrollHeight + 'px'
}
},
computed: {
counter () {
return this.$store.getters.counter
}
}
}
</script>
<style lang="less">
body {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #2c3e50;
margin: 0
}
p{
margin-top: 1em;
margin-bottom: 1em;
}
html, body, #__nuxt, #__layout, .default, .product{
height: 100%;
}
.product {
justify-content: center;
display: flex;
max-width: 1150px;
margin: 0 auto;
flex-wrap: wrap;
align-items: center;
padding: 0 24px;
&-price{
input {
border: 1px solid;
padding: 0 .3em;
text-align: center;
width: 50px;
}
}
}
.product-details{
width: 100%;
textarea {
width: 100%;
font-size: inherit;
color: inherit;
font-family: inherit;
font-weight: inherit;
height: initial;
resize: none;
background-color: transparent;
border: none;
}
h1{
font-size: 1.9rem;
margin: 15px 0 20px;
}
hr{
width: 50%;
margin: .5rem 0px;
}
p{
}
}
.product-description-text{
margin: 10px 0;
}
.product-image, .product-details-wrapper{
align-items: center;
display: flex;
justify-content: center;
}
.product-details-wrapper{
flex: 0 1 535px;
}
.product-image{
flex: 0 1 535px;
img{
width: 100%;
}
}
.product-price{
.strike{
text-decoration: line-through;
}
button{
display: flex;
width: 150px;
height: 50px;
border-radius: 5px;
justify-content: center;
font-size: 24px;
margin-top: 20px;
&:hover{
cursor: pointer;
background-color: #f1f1f1;
box-shadow: 3px 3px 11px -1px rgba(0, 0, 0, 0.48);
}
}
}
.product-sale-price{
color: #f30000;
}
.footer {
flex: 1 1 100%;
text-align: center;
color: #ccc;
margin-top: 25px;
padding: 15px;
a {
color: #ccc;
text-decoration: none;
&:hover{
text-decoration: underline;
}
}
}
.update-product{
position: absolute;
top: 0;
text-align: center;
}
</style>
nuxt.confgs.js
const pkg = require('./package')
const { STRIPE_TOKEN } = process.env;
module.exports = {
vue: {
config: {
productionTip: false,
devtools: true
}
},
buildDir: './functions/nuxt',
mode: 'universal',
/*
** Headers of the page
*/
head: {
title: pkg.name,
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ hid: 'description', name: 'description', content: pkg.description }
],
link: [
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
]
},
/*
** Customize the progress-bar color
*/
loading: { color: '#fff' },
/*
** Global CSS
*/
css: [
],
/*
** Plugins to load before mounting the App
*/
/*
** Nuxt.js modules
*/
modules: [
// Doc: https://github.com/nuxt-community/axios-module#usage
'#nuxtjs/axios',
'nuxt-stripe-module'
],
stripe: {
version: 'v3',
publishableKey: 'pk_test_XXX',
},
/*
** Axios module configuration
*/
axios: {
// See https://github.com/nuxt-community/axios-module#options
},
/*
** Build configuration
*/
build: {
/*
** You can extend webpack config here
*/
publicPath: '/public/',
vendor: [],
extractCSS: true,
bable: {
presets: [
'es2015',
'stage-8'
],
plugins: [
['transform-runtime', {
'polyfill': true,
'regenerator': true
}]
]
},
extend (config, { isDev }) {
if (isDev && process.client) {
config.module.rules.push({
enforce: 'pre',
test: /\.(js|vue)$/,
loader: 'eslint-loader',
exclude: /(node_modules)/
})
}
},
router: {
middleware: 'router-auth'
}
},
plugins: [
{ src: '~/plugins/fireauth', ssr: true }
]
}
store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const createStore = () => {
return new Vuex.Store({
state: () => ({
counter: 0
}),
actions: {
incrementCounterUp () {
this.commit('increment')
}
},
getters: {
counter: state => state.counter
},
mutations: {
increment (state) {
state.counter++
}
}
})
}
export default createStore
At the end of the day, I was not able to identify exactly what in my application was the error.
I assume that my during my initialization, configuration or development I touched something that should not have been touched, installed something that should not have be installed, messed with a package I should not have or was to bold in my nuxt.config.js changes.
I created a new nuxt app following the same install instructions. https://nuxtjs.org/guide/installation/
Moved the above _id.vue component exactly as it is and once I got dependencies updated it worked perfectly as seen in the image below.
Thank you very much #Boussadjra Brahim, #xaviert, #Andrew1325 for you assistance.

Unexpected behaviour removing a child component (row)

Description:
I have a table with some products, each row is a custom vue <row> component.
Each element has a closing (removing) button that triggers the custom "remove" event. The main app listens to this event and removes the children (by index)
The row a part from some static text it contains an input with a number.
The problem:
The parent (Vue app) removes the row, but the value of the input is then moved (and replaces its previous value) to the input in the next row.
Expected behaviour:
I want to simply remove the item I do not care about the value of the text input once it's removed. It should not move its value to the next sibling.
I attach an example.
let row = Vue.component('row', {
name: 'row',
props: ['number', 'name', 'sq'],
data: () => ({
quantity: 0
}),
template: '<tr>' +
'<td>{{number}}</td>' +
'<td>{{name}}</td>' +
'<td><button v-on:click="quantity--">-</button><input type="text" :value="quantity"><button v-on:click="quantity++">+</button></td>' +
'<td><button v-on:click="remove">×</button></td>' +
'</tr>',
methods: {
remove: function() {
this.$emit('remove', this.quantity)
}
},
beforeMount() {
this.quantity = this.sq
}
})
new Vue({
el: "#app",
data: {
out: [],
rows: [{
name: "Icecream",
sq: 0
},
{
name: "Sugar cube",
sq: 50
},
{
name: "Peanut butter",
sq: 0
},
{
name: "Heavy cream",
sq: 0
},
{
name: "Cramberry juice",
sq: 0
}
]
},
methods: {
removeRow: function(index, quantity) {
this.out.push(`Removing row ${index} (${this.rows[index].name} | ${quantity} units)`)
this.rows.splice(index, 1)
}
},
computed: {
log: function() {
return this.out.join("\r\n")
}
}
})
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
h2 {
font-weight: bold;
margin-bottom: 10px;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
td {
padding: 4px 5px;
}
input {
width: 40px;
text-align: center;
}
h4 {
margin-top: 20px;
margin-bottom: 5px;
}
#log {
padding: 10px;
background: #20262E;
color: #fff;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.min.js"></script>
<div id="app">
<h2>Cart:</h2>
<table>
<row v-for="(row, index) in rows" :number="index" :name="row.name" :sq="row.sq" v-on:remove="removeRow(index, $event)"></row>
</table>
<h4>Log</h4>
<pre id="log" v-html="log"></pre>
</div>
As #Bert mentioned in the comments.
The problem was that I was missing a key.
https://v2.vuejs.org/v2/api/#key
Adding it solved the problem
Thanks

Error when trying to use ReactiveProp in vue-chartjs

I'm trying to use a component called "Vue-Chartjs" to create a LineChart.[
I'm passing some data from a MySql database to the 'chartData' prop, defined in the Chart.js file.
But I'm getting this error. What I understood (I think), is that chartData doesn't get updated.
Does anyone know why it doesn't work? Thank you very much!
This are my Chart.js file and my Forecast.vue file
import {
Line,
mixins
} from 'vue-chartjs'
const {
reactiveProp
} = mixins
export default {
name: 'line-chart',
extends: Line,
mixins: [reactiveProp],
props: {
chartData: {
type: Array,
required: true
},
chartLabels: {
type: Array,
required: true
}
},
data() {
return {
options: {
scales: {
yAxes: [{
display: true
}],
xAxes: [{
display: false
}]
},
legend: {
display: true
},
responsive: true,
maintainAspectRatio: false
}
}
},
mounted() {
this.renderChart({
labels: this.chartLabels,
datasets: [{
label: 'Temperature',
colors: '#000000',
backgroundColor: '#000000',
data: this.chartData
}]
}, this.options)
}
}
<template>
<div class="FiveDaysForecast">
<div class="tabs is-fullwidth">
<ul>
<li><router-link to="OneDayForecast"><span class="icon is-small"><i class="fas fa-cloud"></i></span>One day forecast</router-link></li>
<li class="is-active"><router-link to="FiveDaysForecast"><span class="icon is-small"><i class="fas fa-cloud"></i></span>Five days forecast</router-link></li>
<li><router-link to="FilterByDate"><span class="icon is-small"><i class="fas fa-calendar-alt"></i></span>Filter forecast by date</router-link></li>
</ul>
</div>
<h1>FIVE DAYS FORECAST</h1>
<form>
<div class="box">
<div class="field">
<div class="control">
<input class="input is-rounded" type="text" placeholder="Place" v-model="place">
</div>
<br>
<div class="control">
<input class="input is-rounded" type="text" placeholder="Country" v-model="country">
</div>
<br>
<div class="control">
<input class="input is-rounded" type="text" placeholder="Unit of measure (Celsius = 'metric' | Fahrenheit = 'imperial')" v-model="unitOfMeasure" required>
</div>
<br>
<div class="control">
<input class="input is-rounded" type="text" placeholder="Latitude" v-model="latitude">
</div>
<br>
<div class="control">
<input class="input is-rounded" type="text" placeholder="Longitude" v-model="longitude">
</div>
</div>
<button class="button is-medium is-rounded" #click="getFiveDaysForecast">Search</button>
</div>
</form>
<br>
<table class="table is-narrow">
<thead>
<tr>
<th>Temperature</th>
<th>TemperatureMin</th>
<th>TemperatureMax</th>
<th>Humidity</th>
<th>Pressure</th>
<th>Time</th>
</tr>
</thead>
<tbody>
<tr v-for="forecast in forecastData" :key="forecast.Pressure">
<td>{{forecast.temperature}}</td>
<td>{{forecast.temperatureMin}}</td>
<td>{{forecast.temperatureMax}}</td>
<td>{{forecast.humidity}}</td>
<td>{{forecast.pressure}}</td>
<td>{{forecast.timeStamp}}</td>
</tr>
</tbody>
</table>
<line-chart
:chart-data="tempRows"
:chartLabels="weatherDate"
></line-chart>
</div>
</template>
<script>
import axios from "axios";
export default {
name: "FiveDaysForecast",
data() {
return {
place: "",
country: "",
unitOfMeasure: "",
latitude: "",
longitude: "",
forecastData: [],
temperature: [],
weatherDate: [],
tempRows: []
};
},
methods: {
getFiveDaysForecast() {
axios({
method: "get",
url: "http://localhost:55556/api/ForecastActions/fiveDaysForecast",
params: {
Place: this.place,
Country: this.country,
UnitOfMeasure: this.unitOfMeasure,
Lat: this.latitude,
Lon: this.longitude
}
})
.then(response => {
console.log(response);
this.forecastData = response.data;
this.forecastData.forEach(item => {
var tempArray = [];
this.tempRows.push(item.temperature);
this.weatherDate.push(item.timeStamp);
});
})
.catch(error => {
console.log(error);
});
}
}
};
</script>
<style scoped>
.FiveDaysForecast {
width: 1200px;
margin: 30px auto;
}
.tabs {
width: 600px;
margin-left: auto;
margin-right: auto;
}
h1 {
font-size: 50px;
}
#forecast-params {
margin-top: 30px;
margin-bottom: 50px;
text-align: left;
font-size: 20px;
}
.param-names {
font-weight: bold;
color: black;
font-size: 20px;
}
.box {
background-color: transparent;
width: 600px;
margin: 0 auto;
border: 3px solid royalblue;
box-shadow: 0px 0px 30px royalblue;
}
::placeholder {
color: rgb(170, 170, 170);
}
input,
button {
background-color: #fcfcfc;
border: 2px solid rgb(170, 170, 170);
}
input:focus {
border: 2px solid royalblue;
box-shadow: 0px 0px 30px royalblue;
}
input:hover {
border: 2px solid royalblue;
}
button:focus {
border: 2px solid royalblue;
box-shadow: 0px 0px 30px royalblue;
}
button:hover {
border: 2px solid royalblue;
}
.table {
margin: 0 auto;
width: 1000px;
}
td,
th {
text-align: center;
}
thead {
font-size: 20px;
display: table-header-group;
vertical-align: middle;
border-bottom: 3px solid royalblue !important;
}
tr {
font-weight: 600;
transition: background-color 0.5s ease;
}
tr:hover {
background-color: rgb(153, 179, 255);
}
#chart {
display: inline-block;
padding: 0px;
margin: 0px;
}
#tempChart {
padding: 0 auto;
}
#charts {
padding: 0px auto;
}
</style>
Well your "problem" is that your data is async. So the chart will be rendered without any data or without proper data.
You have to put a
v-if="loaded"
on your chart component.
And in your axios call you need to set it to true.