I've seen a few 'solutions' online about this but to no avail for us. We're working with the quasar framework and trying to setup a horizontally wrapped set of that are are draggable (for re-ordering). Below is the basic stub of the code we're working with to get this going. However, we're not able to get the cards to layout horizontally everything is stacked in a list. What are we doing wrong?
<template>
<draggable
v-model="items"
group="items"
direction="horizontal"
item-key="id"
#start="drag = true"
#end="drag = false"
>
<template #item="{ element }">
<q-card class="card">
<q-img src="~assets/temp/bb-auto.png" :alt="`Image ${element.name}`">
<div class="absolute-bottom text-subtitle2 text-center">
Image {{ element.name }}
</div>
</q-img>
</q-card>
</template>
</draggable>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import draggable from 'vuedraggable';
const drag = ref(false);
const items = ref([
{ name: 'item1' },
{ name: 'item2' },
{ name: 'item3' },
{ name: 'item4' },
{ name: 'item5' },
]);
</script>
<style scoped>
.card {
max-height: 200px;
max-width: 200px;
display: flex;
}
</style>
Turns out our overall structure was incorrect and once we sorted that out was able to back this into the draggable. We then had to basically treat our as the row.
<template>
<draggable
v-model="items"
group="items"
class="q-pa-md row items-start q-gutter-md"
direction="horizontal"
item-key="name"
#start="drag = true"
#end="drag = false"
>
<template #item="{ element }">
<q-card class="card">
<q-img src="~assets/temp/bb-auto.png" :alt="`Image ${element.name}`">
<div class="absolute-bottom text-subtitle2 text-center">
Image {{ element.name }}
</div>
</q-img>
</q-card>
</template>
</draggable>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import draggable from 'vuedraggable';
const drag = ref(false);
const items = ref([
{ name: 'item1' },
{ name: 'item2' },
{ name: 'item3' },
{ name: 'item4' },
{ name: 'item5' },
]);
</script>
<style scoped>
.card {
width: 100%;
max-height: 200px;
max-width: 200px;
display: flex;
}
</style>
I'm trying to create Todo List App with Laravel and Vue.js.
I have problem with that title error.
I've watched this video (https://www.youtube.com/watch?v=UHSipe7pSac&t=2482s) for paractcing
And also, I've done until 47:50, but when I wrote a a code listItem.vue, It didn't work as shown in video.
Although I compared with mycode for many times, I couldn't where is wrong.
If you have solution, Please tell me how to fix.
And, What is best way how to debug apps with laravel and Vue.js?
Here is my version
php
7.3.11
vue
vue#3.2.31
listItem.vue
<template>
<div class="item">
<input type="checkbox" #change="updateCheck()" v-model="item.completed" />
<span>{{ item.name }}</span>
<button #click="removeItem()" class="trashcan">
<font-awesome-icon icon="trash" />
</button>
</div>
</template>
<script>
export default {
props: ["item"],
};
</script>
<style scoped>
.completed {
text-decoration: line-through;
color: #999999;
}
.itemTexgt {
width: 100%;
margin-left: 20px;
}
.item {
display: flex;
justify-content: center;
align-items: center;
}
.trashcan {
background: #e6e6e6;
border: none;
color: #ff0000;
outline: none;
}
</style>
app.vue
<template>
<div class="todoListContainer">
<div class="heading">
<h2 id="title">TodoList</h2>
<add-item-form />
</div>
<list-view :items="items" />
</div>
</template>
<script>
import addItemForm from "./addItemForm.vue";
import listView from "./listView.vue";
export default {
components: {
addItemForm,
listView,
},
data: function () {
return {
items: [],
};
},
methods: {
getList() {
axios
.get("api/items")
.then((response) => {
this.items = response.data;
})
.catch((error) => {
console.log(error);
});
},
},
created() {
this.getList();
},
};
</script>
<style scoped>
.todoListContainer {
width: 350px;
margin: auto;
}
.heading {
background: #e6e6e6;
padding: 10px;
}
#title {
text-align: center;
}
</style>
error.log
app.js:34649 Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'completed')
at Proxy.render (app.js:34649:69)
at renderComponentRoot (app.js:22841:44)
at ReactiveEffect.componentUpdateFn [as fn] (app.js:26958:57)
at ReactiveEffect.run (app.js:20778:25)
at setupRenderEffect (app.js:27084:9)
at mountComponent (app.js:26867:9)
at processComponent (app.js:26825:17)
at patch (app.js:26426:21)
at mountChildren (app.js:26613:13)
at mountElement (app.js:26522:17)
postscript
listView.vue
<template>
<div>
<div v-for="(item, index) in items" :key="index"></div>
<list-item :item="item" class="item" />
</div>
</template>
<script>
import listItem from "./listItem.vue";
export default {
props: ["items"],
components: {
listItem,
},
};
</script>
<style scoped>
.item {
background: #e6e6e6;
padding: 5px;
margin-top: 5px;
}
</style>
I learned and understood listItem.vue's parent is listView.vue.
What a silly mistake I had made!
<template>
<div>
<div v-for="(item, index) in items" :key="index"></div>
<list-item :item="item" class="item" />
</div>
</template>
↓
<div v-for="(item, index) in items" :key="index">
<list-item
:item="item"
class="item"
v-on:itemchanged="$emit('reloadlist')"
/>
</div>
sorry to take up your time.
I am trying to test if the dialog created with vuetify will be active or not after I emit a function called closeNoteForm. However, when I try to test the dialog content being hidden or not, I get an error. It seems the problem is within the noteForm wrapper that I created. But it just points to the css and doesn't make much sense to me.
In my NoteForm component
<template>
<v-card class="note-form-container">
<v-text-field
v-model="title"
placeholder="Note Title"
hide-details
class="font-weight-bold text-h5 pa-2"
flat
solo
color="yellow"
>
</v-text-field>
<vue-tags-input
v-model="tag"
:tags="tags"
#tags-changed="(newTags) => (tags = newTags)"
/>
<div class="mt-2">
<input
type="file"
id="uploadImg"
style="display: none"
multiple
accept="image/*"
v-on:change="handleFileUploads"
/>
<label class="text-button pa-2 upload__label ml-3 mt-2" for="uploadImg"
>Upload Image</label
>
</div>
<v-container>
<v-row justify="space-around"
><div v-for="(image, index) in allImages" :key="index">
<div style="position: relative">
<v-img
:src="image"
height="70px"
width="70px"
contain
class="mx-2 uploaded__image"
#click="openImage(image)"
></v-img>
<v-btn
icon
color="pink"
class="close__button"
#click="handleDeleteButton(image)"
>
<v-icon>mdi-close</v-icon>
</v-btn>
</div>
</div>
<v-spacer></v-spacer>
</v-row>
</v-container>
<v-textarea
v-model="text"
clearable
clear-icon="mdi-close-circle"
no-resize
hide-details
solo
flat
></v-textarea>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn text #click="closeForm()" class="close__btn"> Close </v-btn>
<v-btn color="secondary darken-2" text #click="saveNote"> Save </v-btn>
</v-card-actions>
<v-dialog v-model="dialog" width="500" dark>
<v-img :src="selectedImage" #click="dialog = false"></v-img>
</v-dialog>
<v-dialog
v-model="imageDeletionDialog"
width="500"
class="image_delete_dialog"
>
<v-img :src="selectedImage"></v-img>
<v-btn color="red darken-1" text #click="deleteImage">
Delete Image
</v-btn>
<v-btn text #click="imageDeletionDialog = false"> Close </v-btn>
</v-dialog>
</v-card>
</template>
<script>
import { EventBus } from "../event-bus";
import VueTagsInput from "#johmun/vue-tags-input";
import { v4 as uuidv4 } from "uuid";
import dbService from "../services/db_service";
export default {
name: "NoteForm",
components: { VueTagsInput },
data: () => {
return {
text: "",
title: "",
tag: "",
tags: [],
currentNoteID: null,
allImages: [],
dialog: false,
selectedImage: "",
imageDeletionDialog: false,
};
},
mounted() {
EventBus.$on("editNote", (noteToEdit) => {
this.fillNoteForm(noteToEdit);
});
},
methods: {
openImage(image) {
this.selectedImage = image;
this.dialog = true;
},
handleDeleteButton(image) {
this.imageDeletionDialog = true;
this.selectedImage = image;
},
deleteImage() {
this.allImages = this.allImages.filter(
(img) => img !== this.selectedImage
);
this.imageDeletionDialog = false;
this.selectedImage = "";
},
handleFileUploads(e) {
const images = e.target.files;
let imageArray = [];
for (let i = 0; i < images.length; i++) {
const reader = new FileReader();
const image = images[i];
reader.onload = () => {
imageArray.push(reader.result);
};
reader.readAsDataURL(image);
}
this.allImages = imageArray;
e.target.value = "";
},
saveNote() {
if (this.title.trim() === "") {
alert("Please enter note title!");
return;
}
if (this.text === null) this.text = "";
if (this.currentNoteID === null) {
this.createNewNote();
} else {
this.updateNote();
}
this.resetForm();
},
createNewNote() {
const tagList = this.tags.map((tag) => {
return tag.text;
});
const uuid = uuidv4();
const newNote = {
title: this.title,
tags: this.tags,
text: this.text,
uuid,
date: new Date().toLocaleString(),
tagList,
allImages: this.allImages,
};
dbService.addNote(newNote);
EventBus.$emit("addNewNote", newNote);
EventBus.$emit("closeNoteForm");
},
updateNote() {
const tagList = this.tags.map((tag) => {
return tag.text;
});
let updatedNote = {
tags: this.tags,
uuid: this.currentNoteID,
text: this.text,
title: this.title,
tagList: tagList,
allImages: this.allImages,
};
dbService.updateNote(updatedNote);
EventBus.$emit("updateNote", updatedNote);
},
resetForm() {
this.tags = [];
this.text = "";
this.title = "";
this.currentNoteID = null;
this.allImages = [];
this.selectedImage = "";
},
closeForm() {
if (this.currentNoteID !== null) {
EventBus.$emit("openNoteView", this.currentNoteID);
}
this.resetForm();
**EventBus.$emit("closeNoteForm");**
},
fillNoteForm(noteToEdit) {
this.text = noteToEdit.text;
this.title = noteToEdit.title;
this.tags = noteToEdit.tags;
this.currentNoteID = noteToEdit.uuid;
this.allImages = noteToEdit.allImages;
},
},
beforeDestroy() {
EventBus.$off("fillNoteForm", this.fillNoteForm);
},
};
</script>
<style lang="scss" >
.uploaded__image {
position: relative;
cursor: pointer;
}
.close__button {
position: absolute;
top: 0;
right: 0;
}
.upload__label {
background-color: gray;
cursor: pointer;
&:hover {
background-color: lightgrey;
}
}
.note-form-container {
scrollbar-width: none;
}
.v-input__control,
.v-input__slot,
.v-text-field__slot {
height: 100% !important;
}
.v-textarea {
height: 350px !important;
}
.vue-tags-input {
max-width: 100% !important;
border: none;
background: transparent !important;
}
.v-dialog {
background-color: rgb(230, 230, 230);
}
.vue-tags-input .ti-tag {
position: relative;
}
.vue-tags-input .ti-input {
padding: 4px 10px;
transition: border-bottom 200ms ease;
border: none;
height: 50px;
overflow: auto;
}
.note-form-container.theme--dark {
.vue-tags-input .ti-tag {
position: relative;
background: white;
color: black !important;
}
.vue-tags-input .ti-new-tag-input {
color: #07c9d2;
}
}
.note-form-container.theme--light {
.vue-tags-input .ti-tag {
position: relative;
background: black;
color: white !important;
}
.vue-tags-input .ti-new-tag-input {
color: #085e62;
}
}
</style>
My Note component
<template>
<v-dialog width="500px" v-model="dialog">
<template v-slot:activator="{ on, attrs }">
<v-card
v-bind="attrs"
v-on="on"
height="200px"
color="primary"
class="note_card"
>
<v-card-title class="text-header font-weight-bold white--text"
>{{ note.title }}
</v-card-title>
<v-card-subtitle
v-if="note.text.length < 150"
class="text-caption white--text note__subtitle"
>
{{ note.text }}
</v-card-subtitle>
<v-card-subtitle v-else class="text-caption note__subtitle">
{{ note.text.substring(0, 150) + ".." }}
</v-card-subtitle>
</v-card>
</template>
<template>
<v-card class="read_only_note">
<v-card-subtitle class="text-h4 black--text font-weight-bold pa-5"
>{{ note.title }}
</v-card-subtitle>
<v-card-subtitle>
<span
v-for="(note, index) in this.note.tagList"
:key="index"
class="tag_span"
>
{{ note }}
</span>
</v-card-subtitle>
<v-container>
<v-row justify="space-around" class="note__images"
><div v-for="(image, index) in note.allImages" :key="index">
<v-img
:src="image"
height="70px"
width="70px"
contain
class="mx-2"
#click="openImage(image)"
></v-img>
</div>
<v-spacer></v-spacer>
</v-row>
</v-container>
<v-card-subtitle class="text__container">
<p v-html="convertedText" #click="detectYoutubeClick"></p>
</v-card-subtitle>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="red darken-1" text #click="deleteDialog = true">
Delete
</v-btn>
<v-btn color="secondary darken-2" text #click="closeNoteView()">
Close
</v-btn>
<v-btn color="secondary darken-2" text #click="editNote">
Edit
</v-btn>
</v-card-actions>
</v-card>
</template>
<v-dialog v-model="imageDialog" width="500">
<v-img :src="selectedImage" #click="imageDialog = false"></v-img>
</v-dialog>
<v-dialog v-model="deleteDialog" width="500">
<h4 style="text-align: center" class="pa-5">
Are you sure you want to delete {{ note.title }}?
</h4>
<v-btn color="red darken-1" text #click="deleteNote()"> Delete </v-btn>
<v-btn color="blue darken-1" text #click="deleteDialog = false">
Close
</v-btn>
</v-dialog>
<v-dialog v-model="youtubeDialog" width="500">
<iframe
id="ytplayer"
type="text/html"
width="500"
height="400"
:src="youtubeSrc"
frameborder="0"
></iframe>
</v-dialog>
</v-dialog>
</template>
<script>
import { EventBus } from "../event-bus";
export default {
props: {
note: Object,
},
data: () => {
return {
dialog: false,
imageDialog: false,
deleteDialog: false,
selectedImage: "",
youtubeDialog: false,
youtubeSrc: "",
};
},
mounted() {
EventBus.$on("openNoteView", (noteID) => {
if (this.note.uuid === noteID) {
return this.openNoteView();
}
});
},
methods: {
openImage(image) {
this.selectedImage = image;
this.imageDialog = true;
},
deleteNote() {
EventBus.$emit("deleteNote", this.note.uuid);
},
editNote() {
EventBus.$emit("openNoteForm");
this.closeNoteView();
// To load data after note form is mounted
setTimeout(() => {
EventBus.$emit("editNote", this.note);
}, 200);
},
openNoteView() {
this.dialog = true;
},
closeNoteView() {
this.dialog = false;
},
detectYoutubeClick(e) {
if (e.target.innerText.includes("youtube")) {
const url = e.target.innerText.replace("watch?v=", "embed/");
this.youtubeSrc = !url.includes("http") ? "https://" + url : url;
this.youtubeDialog = true;
}
},
},
computed: {
convertedText: function () {
const urlRegex = /(((https?:\/\/)|(www\.))[^\s]+)/g;
return this.note.text.replace(urlRegex, function (url, b, c) {
const url2 = c == "www." ? "http://" + url : url;
if (url2.includes("youtube")) {
return `<span class="youtube_url">${url}</span>`;
} else {
return '' + url + "";
}
});
},
},
watch: {
youtubeDialog: function (newVal) {
if (newVal === false) this.youtubeSrc = "";
},
},
beforeDestroy() {
EventBus.$off("openNoteView", this.openNoteView);
},
};
</script>
<style lang="scss" >
.text__container {
height: 50vh;
p {
width: 100%;
}
}
.note__images {
margin-left: 5px !important;
}
.note_card {
border: 1px solid black !important;
}
.theme--dark.v-card .v-card__title {
color: black !important;
}
.theme--dark.v-card .v-card__subtitle.note__subtitle {
color: black !important;
}
.theme--light.v-card .v-card__subtitle.note__subtitle {
color: white !important;
}
.read_only_note.theme--dark {
.v-card__subtitle {
color: white !important;
display: flex;
flex-wrap: wrap;
}
.tag_span {
background-color: white;
color: black !important;
padding: 2px;
border-radius: 3px;
margin: 5px;
}
}
.read_only_note.theme--light {
.v-card__subtitle {
display: flex;
flex-wrap: wrap;
}
.tag_span {
color: white !important;
background-color: #212121;
padding: 2px;
border-radius: 3px;
margin: 5px;
}
}
.youtube_url {
text-decoration: underline;
&:hover {
cursor: pointer;
}
}
</style>
My spec file
import Vue from 'vue';
Vue.use(Vuetify);
import Vuetify from 'vuetify';
import NoteForm from '#/components/NoteForm';
import Note from '#/components/Note';
import { createLocalVue, mount } from '#vue/test-utils';
describe('NoteForm.vue', () => {
const localVue = createLocalVue();
localVue.use(Vuetify);
document.body.setAttribute('data-app', true);
let vuetify;
beforeEach(() => {
vuetify = new Vuetify();
});
it('should emit an event when the action v-btn is clicked', async () => {
const formWrapper = mount(NoteForm, {
localVue,
vuetify,
});
const noteWrapper = mount(Note, {
localVue,
vuetify,
});
formWrapper.vm.$emit('closeNoteForm');
await formWrapper.vm.$nextTick(); // Wait until $emits have been handled
expect(formWrapper.emitted().closeNoteForm).toBeTruthy();
expect(noteWrapper.find('.read_only_note').isVisible()).toBe(true);
});
});
However im getting the error
[Vue warn]: Error in render: "TypeError: Cannot read property 'title' of undefined"
found in
---> <Anonymous>
<Root>
console.error node_modules/vue/dist/vue.runtime.common.dev.js:1884
TypeError: Cannot read property 'title' of undefined
at Proxy.render (/Users/ozansozuoz/Downloads/vue-notebook/src/components/Note.vue:199:832)
at VueComponent.Vue._render (/Users/ozansozuoz/Downloads/vue-notebook/node_modules/vue/dist/vue.runtime.common.dev.js:3538:22)
at VueComponent.updateComponent (/Users/ozansozuoz/Downloads/vue-notebook/node_modules/vue/dist/vue.runtime.common.dev.js:4054:21)
at Watcher.get (/Users/ozansozuoz/Downloads/vue-notebook/node_modules/vue/dist/vue.runtime.common.dev.js:4465:25)
at new Watcher (/Users/ozansozuoz/Downloads/vue-notebook/node_modules/vue/dist/vue.runtime.common.dev.js:4454:12)
at mountComponent (/Users/ozansozuoz/Downloads/vue-notebook/node_modules/vue/dist/vue.runtime.common.dev.js:4061:3)
at VueComponent.Object.<anonymous>.Vue.$mount (/Users/ozansozuoz/Downloads/vue-notebook/node_modules/vue/dist/vue.runtime.common.dev.js:8392:10)
at init (/Users/ozansozuoz/Downloads/vue-notebook/node_modules/vue/dist/vue.runtime.common.dev.js:3112:13)
at createComponent (/Users/ozansozuoz/Downloads/vue-notebook/node_modules/vue/dist/vue.runtime.common.dev.js:5958:9)
at createElm (/Users/ozansozuoz/Downloads/vue-notebook/node_modules/vue/dist/vue.runtime.common.dev.js:5905:9)
at VueComponent.patch [as __patch__] (/Users/ozansozuoz/Downloads/vue-notebook/node_modules/vue/dist/vue.runtime.common.dev.js:6455:7)
at VueComponent.Vue._update (/Users/ozansozuoz/Downloads/vue-notebook/node_modules/vue/dist/vue.runtime.common.dev.js:3933:19)
at VueComponent.updateComponent (/Users/ozansozuoz/Downloads/vue-notebook/node_modules/vue/dist/vue.runtime.common.dev.js:4054:10)
at Watcher.get (/Users/ozansozuoz/Downloads/vue-notebook/node_modules/vue/dist/vue.runtime.common.dev.js:4465:25)
at new Watcher (/Users/ozansozuoz/Downloads/vue-notebook/node_modules/vue/dist/vue.runtime.common.dev.js:4454:12)
at mountComponent (/Users/ozansozuoz/Downloads/vue-notebook/node_modules/vue/dist/vue.runtime.common.dev.js:4061:3)
at VueComponent.Object.<anonymous>.Vue.$mount (/Users/ozansozuoz/Downloads/vue-notebook/node_modules/vue/dist/vue.runtime.common.dev.js:8392:10)
at mount (/Users/ozansozuoz/Downloads/vue-notebook/node_modules/#vue/test-utils/dist/vue-test-utils.js:13977:21)
at Object.<anonymous> (/Users/ozansozuoz/Downloads/vue-notebook/tests/unit/example.spec.js:24:25)
at Object.asyncJestTest (/Users/ozansozuoz/Downloads/vue-notebook/node_modules/jest-jasmine2/build/jasmineAsyncInstall.js:102:37)
at /Users/ozansozuoz/Downloads/vue-notebook/node_modules/jest-jasmine2/build/queueRunner.js:43:12
at new Promise (<anonymous>)
at mapper (/Users/ozansozuoz/Downloads/vue-notebook/node_modules/jest-jasmine2/build/queueRunner.js:26:19)
at /Users/ozansozuoz/Downloads/vue-notebook/node_modules/jest-jasmine2/build/queueRunner.js:73:41
at processTicksAndRejections (internal/process/task_queues.js:93:5)
FAIL tests/unit/example.spec.js
NoteForm.vue
✕ should emit an event when the action v-btn is clicked (501ms)
● NoteForm.vue › should emit an event when the action v-btn is clicked
TypeError: Cannot read property 'title' of undefined
197 | }
198 | .read_only_note.theme--dark {
> 199 | .v-card__subtitle {
| ^
200 | color: white !important;
201 | display: flex;
202 | flex-wrap: wrap;
at Proxy.render (src/components/Note.vue:199:832)
at VueComponent.Vue._render (node_modules/vue/dist/vue.runtime.common.dev.js:3538:22)
at VueComponent.updateComponent (node_modules/vue/dist/vue.runtime.common.dev.js:4054:21)
at Watcher.get (node_modules/vue/dist/vue.runtime.common.dev.js:4465:25)
at new Watcher (node_modules/vue/dist/vue.runtime.common.dev.js:4454:12)
at mountComponent (node_modules/vue/dist/vue.runtime.common.dev.js:4061:3)
at VueComponent.Object.<anonymous>.Vue.$mount (node_modules/vue/dist/vue.runtime.common.dev.js:8392:10)
at init (node_modules/vue/dist/vue.runtime.common.dev.js:3112:13)
at createComponent (node_modules/vue/dist/vue.runtime.common.dev.js:5958:9)
at createElm (node_modules/vue/dist/vue.runtime.common.dev.js:5905:9)
at VueComponent.patch [as __patch__] (node_modules/vue/dist/vue.runtime.common.dev.js:6455:7)
at VueComponent.Vue._update (node_modules/vue/dist/vue.runtime.common.dev.js:3933:19)
at VueComponent.updateComponent (node_modules/vue/dist/vue.runtime.common.dev.js:4054:10)
at Watcher.get (node_modules/vue/dist/vue.runtime.common.dev.js:4465:25)
at new Watcher (node_modules/vue/dist/vue.runtime.common.dev.js:4454:12)
at mountComponent (node_modules/vue/dist/vue.runtime.common.dev.js:4061:3)
at VueComponent.Object.<anonymous>.Vue.$mount (node_modules/vue/dist/vue.runtime.common.dev.js:8392:10)
at mount (node_modules/#vue/test-utils/dist/vue-test-utils.js:13977:21)
at Object.<anonymous> (tests/unit/example.spec.js:24:25)
I don't understand why its pointing to my scss and saying undefined?
You don't add props to your wrapper in tests.
Add this code to your test.
const noteWrapper = mount(Note, {
localVue,
vuetify,
propsData: {
note: {
title: '',
}
});
I have a problem related to Vue.js probs (I guess). I have a chart which fetches data in the created() from an API which looks like this:
<template>
<div>
<highcharts if="data.length > 0" :options="chartOptions" ></highcharts>
</div>
</template>
<script>
import { Chart } from "highcharts-vue";
import Highcharts from "highcharts";
import exportingInit from "highcharts/modules/exporting";
exportingInit(Highcharts);
export default {
props: {
partsdata: {
type: Array
}
},
created() {
this.$http.get('http://127.0.0.1:5000/data/')
.then(response => {
var result = JSON.parse(response.data)
return result
}).then(data => {
var result = data.Fare
console.log(result) // eslint-disable-line no-console
this.chartOptions.series[0].data = [] // Array wieder zurücksetzen
for(var key in result) {
this.chartOptions.series[0].data.push(result[key]) // eslint-disable-line no-console
}
})
},
components: {
highcharts: Chart
},
data() {
return {
data: [],
chartOptions: {
title: {
text: 'Title aligned left',
align: 'left',
x: 0
},
series: [{
type: "column",
data: [] // sample data
}]
}
}
}
};
</script>
<style scoped>
.btn-primary {
padding: 10px 20px;
background: white;
color: black;
margin-top: 15px;
margin-bottom: 15px;
border-radius: 5px;
}
</style>
This is my parent component, where I use my Charts, which works fine.
<template>
<v-container grid-list-md text-xs-center>
<v-layout row wrap>
<v-flex xs6>
<v-card dark color="white">
<v-card-text class="px-0">
<Highcharts></Highcharts>
</v-card-text>
</v-card>
</v-flex>
<v-flex xs6>
<v-card dark color="white">
<v-card-text class="px-0">
<Highcharts></Highcharts>
</v-card-text>
</v-card>
</v-flex>
<v-flex xs6>
<v-card dark color="white">
<v-card-text class="px-0">
<Highcharts></Highcharts>
</v-card-text>
</v-card>
</v-flex>
<v-flex xs6>
<v-card dark color="white">
<v-card-text class="px-0">
<Highcharts></Highcharts>
</v-card-text>
</v-card>
</v-flex>
</v-layout>
</v-container>
</template>
<script>
import Highcharts from "./Header.vue"
export default {
components: {
Highcharts: Highcharts
}
}
</script>
<style scoped>
</style>
However, what I want is to fetch the data from different Endpoints without creating a ton of new components. Is there a way to use the created() lifecycle hook as props or are there any other clever solutions how to archieve this?
This is warning when i click on go to contact in tab about: "Property or method "switchTo" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option.
(found in component )."
How do I fix this?
new Vue({
el: '#app',
data: {
currentPage: 'home',
},
methods: {
switchTo: function(page) {
this.currentPage = page;
}
},
components: {
home: {
template: `#home`,
},
about: {
template: `#about`,
},
contact: {
template: '#contact'
}
}
})
.navigation {
margin: 10px 0;
}
.navigation ul {
margin: 0;
padding: 0;
}
.navigation ul li {
display: inline-block;
margin-right: 20px;
}
input, label, button {
display: block
}
input, textarea {
margin-bottom: 10px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.10/vue.js"></script>
<div id="app">
<div class="navigation">
<ul>
<li>Home</li>
<li>About</li>
</ul>
</div>
<div class="pages">
<keep-alive>
<component v-bind:is="currentPage">
</component>
</keep-alive>
</div>
</div>
<template id="home">
<p>home</p>
</template>
<template id="about">
<p>about go to contact</p>
</template>
<template id="contact">
<p>contact</p>
</template>
Just change your about template to this
<template id="about">
<p>about go to contact</p>
</template>
new Vue({
el: '#app',
data: {
currentPage: 'home',
},
methods: {
switchTo: function(page) {
this.currentPage = page;
}
},
components: {
home: {
template: `#home`,
},
about: {
template: `#about`,
},
contact: {
template: '#contact'
}
}
})
.navigation {
margin: 10px 0;
}
.navigation ul {
margin: 0;
padding: 0;
}
.navigation ul li {
display: inline-block;
margin-right: 20px;
}
input, label, button {
display: block
}
input, textarea {
margin-bottom: 10px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.10/vue.js"></script>
<div id="app">
<div class="navigation">
<ul>
<li>Home</li>
<li>About</li>
</ul>
</div>
<div class="pages">
<keep-alive>
<component v-bind:is="currentPage">
</component>
</keep-alive>
</div>
</div>
<template id="home">
<p>home</p>
</template>
<template id="about">
<p>about go to contact</p>
</template>
<template id="contact">
<p>contact</p>
</template>
I already solved a problem like this in this question: Calling methods in Vue build
It's not the same problem so it's not a repeated question, but the answer is the same:
In the created hook, add the component to window.componentInstance like this:
methods: {
foo () {
console.log('bar')
}
},
created () {
window.componentInstance = this
}
Then you can call the method anywhere like this:
window.componentInstance.foo()