Vue - passing apollo query result from parent to child component - vuejs2

I have this parent component which makes a call to this apollo query:
<template>
<div class="row" style="flex-wrap: nowrap; width:102%">
<BusinessContextTeamPanel :business_contexts="business_contexts"></BusinessContextTeamPanel></BusinessContextTeamDetailPanel>
</div>
</template>
<script>
apollo: {
business_contexts: {
query: gql`query ($businessContextId: uuid!) {
business_contexts(where: {id: {_eq: $businessContextId }}) {
id
businessContextTeams {
id
team {
name
id
}
role
}
}
}`,
variables: function() {
return {
businessContextId: this.currentContextId
}
}
}
},
</script>
My child component( BusinessContextTeamPanel) takes the input props and uses it like this:
<template>
<div v-if="business_contexts.length > 0">
<div v-for="teams in business_contexts" :key="teams.id">
<div v-for="team in teams.businessContextTeams" :key="team.id" style="padding-top: 20px;min-width: 400px">
<div style="height: 25px; color: #83d6c0;">
<div style="width: 7%; font-size: 10px; float: left; padding-top: 8px; cursor: pointer;" class="fa fa-minus-circle" ></div>
<div style="width: 50%; float: left; padding-right: 10px;">
<div class="gov-team-text" #click="editTeam(team)">{{ team.team.name }}</div>
</div>
<div style="width: auto; float: right;">
<b-badge variant="light">{{ team.role }}</b-badge>
</div>
<div style="width: 45%; margin-left: 4%; float: right;">
</div>
</div>
<div style="clear: both; margin-bottom: 15px;"></div>
</div>
</div>
</div>
</template>
The v-if here throws
Cannot read property 'length' of undefined
while the v-for works.
And when I'm trying to use the props in a child method(loadData()) which is called on child component mount hook, I get also undefined
<script>
import Treeselect from '#riophae/vue-treeselect';
import _ from "lodash";
export default {
name: "BusinessContextTeamPanel",
components: { Treeselect },
props: ['business_contexts'],
data() {
return {
itemModel: {},
allRoles: [],
formTitle: "",
filteredTeams: [],
showNoTeamMsg: false
}
},
mounted() {
this.loadData();
},
methods: {
loadData() {
let roles = [];
_.forEach(["Admin", "Contributor", "Owner", "Master", "Viewer", "User"], function (role) {
roles.push({id: role, label: role});
});
console.log(this.business_contexts); // here I get undefined
this.allRoles = roles;
this.showNoTeamMsg = this.business_contexts.length === 0;
}
}
}
</script>
What am I missing?

Either your apollo query isn't working, or it isn't ready in time for the child component's attempted use of it.
For debugging, in the parent component's template add {{ business_contexts }}, just so you can eyeball it. This will confirm whether the query is working.
If the query is working, it could be that your child component is trying to access the data before it's ready. Change your v-if from
<div v-if="business_contexts.length > 0">
to
<div v-if="business_contexts && business_contexts.length > 0">
EDIT: better yet, change the parent component to not try to load the child at all, until the parent has the data:
<BusinessContextTeamPanel v-if="business_contexts" :business_contexts="business_contexts"></BusinessContextTeamPanel></BusinessContextTeamDetailPanel>

Related

Horizontally draggable quasar q-cards using vue.draggable.next

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>

Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'completed') in Vue 3

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.

Nuxt - Using router.push inside a component not changing pages correctly

index.vue -
<template>
<div>
<Header />
<div class="container">
<SearchForm />
</div>
</div>
</template>
<script>
const Cookie = process.client ? require('js-cookie') : undefined
export default {
data() {
return {
form: {
email: '',
password: ''
},
show: true
}
},
methods: {
logout() {
// Code will also be required to invalidate the JWT Cookie on external API
Cookie.remove('auth')
this.$store.commit('setAuth', {
auth: null,
user_type: null
})
}
}
}
</script>
<style>
.container {
/* margin: 0 auto; */
/* min-height: 100vh; */
display: flex;
justify-content: center;
align-items: center;
text-align: center;
}
</style>
jobs.vue -
<template>
<div>
<Header />
<SearchForm />
<b-container class="main_container">
<b-row>
<h1> Results for "{{q}}"</h1>
</b-row>
<b-row>
<ul id="array-rendering">
<li v-for="item in results" :key="item.job_id">
{{ item.job_title }}
{{ item.job_city }}
{{ item.job_state }}
{{ item.job_work_remote }}
</li>
</ul>
</b-row>
</b-container>
</div>
</template>
<script>
const Cookie = process.client ? require('js-cookie') : undefined
export default {
// middleware: 'notAuthenticated',
watchQuery: ['q'],
data() {
return {
q: null,
results: []
}
},
async fetch() {
this.q = this.$route.query.q
this.results = await this.$axios.$get('/api/job/search', {
params: {
keyword: this.q,
}
})
},
methods: {
}
}
</script>
<style>
.container {
align-items: center;
text-align: center;
}
</style>
SearchForm.vue component -
<template>
<div id='searchFormDiv'>
<b-form inline #submit.prevent="onSubmit">
<label class="sr-only" for="inline-form-input-name"> keyword</label>
<b-form-input v-model="form.keyword" id="inline-form-input-name" class="mb-2 mr-sm-2 mb-sm-0" placeholder="Job title or keyword" size="lg"></b-form-input>
<label class="sr-only" for="inline-form-input-username">location</label>
<b-input-group class="mb-2 mr-sm-2 mb-sm-0">
<b-form-input v-model="form.location" id="inline-form-input-username" size="lg" placeholder="City, state or zip"></b-form-input>
</b-input-group>
<b-button type="submit" variant="primary">Find Jobs</b-button>
</b-form>
</div>
</template>
<script>
import {
BIconSearch,
BIconGeoAlt
} from 'bootstrap-vue'
export default {
data() {
return {
form: {
keyword: '',
location: ''
}
}
},
created () {
this.form.keyword = this.$route.query.q
},
methods: {
onSubmit() {
this.$router.push({
path: 'jobs',
query: {
q: this.form.keyword
}
});
}
},
components: {
BIconSearch,
BIconGeoAlt
},
}
</script>
<style>
#searchFormDiv {
margin-top: 50px
}
</style>
The route for "http://localhost:3000/" returns the index.vue page.
In this vue page, I have a component with a search form. Once you complete these form and hit the seach button, it will re-direct to a results page
if this.form.keyword = "Data", the next URL will be "http://localhost:3000/jobs?q=Data" and it will be using the jobs.vue page.
The issue I'm running into is the CSS is not being loaded from the jobs.vue page. It's still coming from the index.vue page for some reason. If I refresh the page, then the CSS from jobs.vue is loading. I need the CSS to load from jobs.vue on the initial redirect. All of the query data is working as expected so thats a plus.
However, the following CSS is being applied from index.vue for some reason instead of the CSS from the jobs.vue page -
display: flex;
justify-content: center;
Does anyone know whats going on here? This app is SSR and not SPA.
You have to scope your css from the index.vue page to the other pages with the scoped directive (see docs https://vue-loader.vuejs.org/guide/scoped-css.html)
<style scoped>
/* local styles */
</style>
<style>
/* global styles */
</style>
You can add your global CSS in your layouts/default.vue file.
This solved the issue -
methods: {
onSubmit() {
window.location = 'http://localhost:3000/jobs?q=' + this.form.keyword;
}
},

Why does Bootstrap Autocomplete send vue.js router go to /#/exclamationmark?

Why does it happen that my Vue router navigates to /#/! without apparent reason?
This seems to happen when I fire an event from an autocomplete form built with Bootstrap Autocomplete and trigger a function.
Calling the same function by clicking a button does not lead to the problem.
This is the parent component where the event is emitted to
<style scoped>
</style>
<template>
<div id="appspace">
<div id="leftbar">
</div>
<div id="workarea">
<div id="mapblock">
</div>
<div id="infoblock">
<div class="form-group"><label for="gotoff">Go to</label>
<autosuggest #locselect="locSelect($event)" id="gotoff"></autosuggest>
</div>
<button v-on:click="searchAround()" type="button" class="btn btn-primary">Search</button>
</div>
</div>
<div id="rightbar">
</div>
</div>
</template>
<script>
module.exports = {
data: function () {
return {
};
},
components: {
autosuggest: httpVueLoader('components/base/autosuggest.vue'),
},
mounted: function(){
},
destroyed: function(){
},
methods: {
setMarkerInCenter: function(){
this.locSelect({ value: { lng: 12, lat: 14 }})
},
locSelect: function(e) {
console.log('locSelect');
console.log(e);
},
},
}
</script>
and this is the component emitting the event:
<style scoped>
.autocomplete {
position: relative;
width: 130px;
}
.autocomplete-results {
padding: 0;
margin: 0;
border: 1px solid #eeeeee;
height: 120px;
overflow: auto;
}
.autocomplete-result {
list-style: none;
text-align: left;
padding: 4px 2px;
cursor: pointer;
}
.autocomplete-result:hover {
background-color: #4AAE9B;
color: white;
}
</style>
<template>
<div class="input-group">
<input ref="ac" class="form-control">
<div class="input-group-append">
<div class="input-group-text"><i class="fa fa-compass" style="height:0.5em;padding:0;margin:0;margin-bottom:4px"></i></div>
</div>
</div>
</template>
<script>
module.exports = {
mounted: function(){
var i = this.$refs.ac;
var c = this
$(i).autoComplete({ resolverSettings: { url: '/api/gc/autocomplete' } });
$(i).on('autocomplete.select', function(e, sel) {
e.preventDefault();
c.$emit('locselect', sel);
e.preventDefault();
});
},
}
</script>
Any leads as to how to debug this?
I couldn't find the reason why this autocomplete changes the route, that behavoir
seems to be undocumented. But here's a method to temporarily prevent this behavior until you find the solution, add this global route guard to your router to prevent navigation to '/#/!' route:
router.js
const router = new VueRouter({ ... })
router.beforeEach((to, from, next) => {
console.log(to)
// TEMP PATCH:
// Autocomplete changes route to '/#/!'
if (to.path !== '/#/!') {
next()
}
})
Just make sure that console.log(to) actually has a property path === '/#/!'

Use method in template, out of instance vue

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()