Vue-Apollo and query autocomplete - vue.js

I'm new to Vue and i'm trying now to do a query update on q-autocomplete component (i have to use Quasar Framework).
This is the FormAdmin component:
<template>
<q-form #submit="submit" #reset="onReset" class="q-gutter-md">
<q-select
filled
v-model="form.UserId"
label="User Email"
use-input
hide-selected
fill-input
input-debounce="0"
:options="options"
#filter="filterFn"
hint="Mininum 2 characters to trigger autocomplete"
style="width: 250px; padding-bottom: 32px"
>
<template v-slot:no-option>
<q-item>
<q-item-section class="text-grey">
No results
</q-item-section>
</q-item>
</template>
</q-select>
<div>
<q-btn label="Submit" type="submit" color="primary"/>
<q-btn label="Reset" type="reset" color="primary" flat class="q-ml-sm" />
</div>
</q-form>
</template>
This is the basic code for the update function into the filterFn:
<script>
export default {
data () {
return {
model: null,
options: null,
}
},
props: ['form', 'submit'],
methods: {
filterFn (val, update, abort) {
if (val.length < 3) {
abort()
return
}
update(() => {
console.log(val);
})
},
onReset(){
},
}
}
</script>
I tried this code:
<script>
import gql from 'graphql-tag';
const stringOptions = gql`
query {
filterUsersListFirst {
UserEmail
}
}`
export default {
data () {
return {
model: null,
options: Object.values(stringOptions),
}
},
props: ['form', 'submit'],
methods: {
filterFn (val, update, abort) {
if (val.length < 3) {
abort()
return
}
this.$apollo.query({
query: gql`query($email: String!) {
filterUsersListByEmail(
email: $email
) {
UserEmail
}
}`,
variables: {
email: val,
}
}).then(data => {
console.log(JSON.stringyfy(data));
this.options = Object.values(data);
}).catch(error =>{
console.log({error});
});
},
onReset(){
},
}
}
</script>
I tried graphql query server-side and it works.
Also i tried to print data that the Promise returns and it results in:
{"data":{"filterUsersListByEmail":[{"UserEmail":"email1","__typename":"User"},{...}]},"loading":false,"networkStatus":7,"stale":false}
But i don't know how to append the query result in the q-select options.

I found myself the solution and i'll write it:
methods: {
filterFn (val, update, abort) {
if (val.length < 3) {
abort()
return
}
setTimeout(() => {
update(() => {
this.$apollo.query({
query: gql`query($email: String!) {
filterUsersListByEmail(
email: $email
) {
UserId
UserEmail
}
}`,
variables: {
email: val,
}
}).then(data => {
var emailList = [];
for(var i = 0; i < data.data.filterUsersListByEmail.length; i++)
{
emailList[i] = JSON.parse('{"label":"' + data.data.filterUsersListByEmail[i].UserEmail + '", "value":"' + data.data.filterUsersListByEmail[i].UserId + '"}');
}
this.options = usersList;
}).catch(error =>{
console.log({error});
});
})
}, 1000)
},
onReset(){
...
},
}

Related

How to Filter over Numbers? (Vuejs, API)

I'm new to Programming and Vuejs. Here's my problem:
I created a Searchbar for my Website and user.title.toLowerCase().includes(this.searchstring.toLowerCase())works fine, but how can i search for numbers in my Searchbar?
Here is my HTML/TailwindCSS part for the SearchBar: <input type="text" v-model="searchstring" placeholder="Suche" class="border-0 border-b-2 border-gray-500 px-2 py-1 rounded-lg focus:outline-none focus:border-blue-500" />
export default {
components: {
TopNavbar,
},
name: "app",
data() {
return {
searchstring:"",
apiurl: "https://MYAPI/api/users/",
usersdata: [],
noPicture: "https://MYAPI/userpictures/default-m.jpg",
view: false,
}
} ,
watch: {
searchstring: function(newSearchstring) {
console.log(newSearchstring)
}
},
computed: {
usersdata() {
const compare = (a, b) => {
if (a.title < b.title) return -1;
if (a.title > b.title) return 1;
return 0;
};
return this.usersdata
.filter((user) => {
return user.title.toLowerCase().includes(this.searchstring.toLowerCase()) || user.id.toLowerCase().includes(this.searchstring.toLowerCase())
})
.sort(compare);
},
},
methods: {
async getdatafromapi() {
const response = await fetch(this.apiurl);
const data = await response.json();
console.log(data)
this.usersdata = data
},
},
methodToRunOnSelected(option) {
this.object = option;
},
async mounted() {
await this.getdatafromapi()
}
}

Error when attempting to disable Navigation Buttons in VueJs

In my ticket processing application I currently have a back and forward button contained in my TicketRunner.vue Component, I would like to change it so that these buttons only appear if I have an associated case file, for which I've used V-If:
TicketRunner.Vue
<div class="level nav-btns" v-if='!currentTicketCaseFiles.length'>
<div class="buttons has-addons level-left">
<b-button
#click.prevent="navGoPrev()"
:disabled="currentStepIndex === 0 || navWaiting"
size="is-medium"
>
</div>
export default {
name: 'TicketRunner',
mixins: [NavStepsByIndexMixin()],
components: {
StagePresenter,
CaseFilesStage,
ParticipantsStage,
AttachmentsStage,
CaseFilesRunner,
TicketContextButtons,
},
data: function() {
return {
firstComponentsInitialization: true,
loadingConfirm: false,
confirmationModalActive: false,
confirmationSucceeded: undefined
}
},
props: {
ticketId: {
type: Number,
required: true,
},
},
provide() {
return {
contextButtons: {
capture: (name, callback, title) => this.$refs['contextButtons'].captureButton(name, callback, title),
release: (name) => this.$refs['contextButtons'].releaseButton(name),
enable: (name) => this.$refs['contextButtons'].enableButton(name),
disable: (name) => this.$refs['contextButtons'].disableButton(name),
},
};
},
computed: {
...mapGetters(['currentTicket', 'ticketCaseFiles', 'allCurrentTicketAttachments', 'currentTicketCaseFileNotAssociated',
'currentRequesterType', 'currentTicketStage', 'lastCaseFile']),
caseFiles() {
return this.ticketCaseFiles(this.ticketId);
},
ticketHasAttachments() {
return this.allCurrentTicketAttachments.length > 0;
},
isTicketAssociatedWithCaseFile() {
return !this.currentTicketCaseFileNotAssociated;
},
isFirstNavInitializationInProgress() {
return !this.navReady && this.firstComponentsInitialization;
},
isShowAttachmentsStep() {
return this.ticketHasAttachments && this.currentRequesterType !== 'unknown' &&
(this.isFirstNavInitializationInProgress || this.isTicketAssociatedWithCaseFile)
},
isCurrentTicketResolved() {
return this.currentTicket.status === 'resolved';
},
islastStep() {
return this.navLastStep() && this.lastCaseFile;
}
},
watch: {
ticketId(){
this.navigator.reset();
},
navReady() {
this.moveForwardIfReady();
this.firstComponentsInitialization = false;
}
},
methods: {
...mapActions(['confirmTicket']),
moveForwardIfReady() {
if (this.navigator.currentIndex === 0 && this.firstComponentsInitialization) {
let steps = 0
const step_names = ['case_files_stage']
for(const [_idx, name] of step_names.entries()) {
const ref_name = `step[${name}]`;
if (this.$refs.hasOwnProperty(ref_name) && this.$refs[ref_name].navReady) {
steps += 1
} else {
break
}
}
this.navigator.currentIndex += steps
}
},
confirm() {
this.$buefy.dialog.confirm({
message: this.t('tickets.stages.confirmation.simplified_confirm_reply'),
onConfirm: () => this.confirmStep()
})
},
async confirmStep() {
this.loadingConfirm = true;
const promise = this.confirmTicket(this.ticketId);
return promise.then((response) => {
this.confirmationModalActive = true;
this.confirmationSucceeded = true;
return true; // true is correct here. for goNext it makes parent to stay on on the current step
}).catch(() => {
this.confirmationModalActive = true;
this.confirmationSucceeded = false;
return true; // true is correct here. for goNext it makes parent to stay on on the current step
}).finally(() => this.loadingConfirm = false);
},
},
};
I then receive the following Console Error:
[Vue warn]: Property or method "currentTicketCaseFiles" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property.
I know that "!currentTicketCaseFiles.length" works successfully in the Component CaseFilesStage.vue, which makes me believe I should somehow connect the two? But importing it doesn't seem right to me either. I'm not quite sure how to tackle this issue as I'm quite new at VueJS, and would be happy for any pointers. I'll attach the CaseFilesStage.vue Component below.
CaseFilesStage.vue
<template>
<div class="hero">
<div class="block">
<template v-if="!currentTicket.spamTicket">
<b-field>
<b-input
v-model="filter"
:loading="loading"
:placeholder="t('tickets.stages.case_files.search.tooltip')"
v-on:keyup.enter.native="searchCaseFiles"
type="search"
icon="search"
:class="{ 'preview-enabled': showAttachmentsPreview}"
/>
</b-field>
<template v-if="foundCaseFiles.length">
<h4 class="title is-4 table-title">{{ t('tickets.stages.case_files.search.table_title') }}</h4>
<CaseFilesSearchTable
:case-files="foundCaseFilxes"
:found-by-data-points="foundCaseFilesByParticipant"
:show-header="true"
v-slot="cf">
<b-checkbox v-if="cfBelongsToCurrentTicket(cf.id)" :disabled="true" :value="true"></b-checkbox>
<b-checkbox v-else #input="onFoundCaseFile(cf.id, $event)"></b-checkbox>
</CaseFilesSearchTable>
</template>
<div v-else-if="lookupStatus === 'notFound'">
{{ t('tickets.stages.case_files.search.not_found') }}
<!-- display button here if above is activated -->
</div>
</template>
</div>
<template v-if='currentTicketCaseFiles.length'>
<h4 class="title is-4 table-title">{{ t('tickets.stages.case_files.table_title') }}</h4>
<CaseFilesTable :case-files="currentTicketCaseFiles" :show-header="true" v-slot="cf">
<DeleteButton
:model-id="cf.id"
modelName="CaseFile" >
</DeleteButton>
</CaseFilesTable>
</template>
</div>
</template>
<script>
import CaseFilesTable from '../tables/CaseFilesTable';
import CaseFilesSearchTable from '../tables/CaseFilesSearchTable';
import DeleteButton from '../../../../shared/components/controls/DeleteButton';
import { mapGetters, mapActions } from 'vuex';
import { mapServerActions } from "../../../../../../_frontend_infrastructure/javascript/lib/crudvuex_new";
export default {
name: 'CaseFilesStage',
data() {
return {
lookupStatus: 'waitingInput',
filter: '',
waiting: {},
foundCaseFiles: [],
foundCaseFilesByParticipant: {}
};
},
components: {
CaseFilesTable,
CaseFilesSearchTable,
DeleteButton
},
computed: {
...mapGetters(
['currentTicketCaseFiles', 'currentTicketCaseFileNotAssociated', 'currentTicket', 'showAttachmentsPreview']
),
loading() {
return this.lookupStatus === 'waitingServer';
},
allCaseFilesMix(){
this.currentTicketCaseFiles + this.foundCaseFiles
},
foundCaseFilesEx() {
return this.foundCaseFiles.filter((x) => !this.cfBelongsToCurrentTicket(x.id))
},
checkboxValue() {
if(!this.currentTicketCaseFileNotAssociated) {
return null;
}
return true;
},
navReady() {
return this.currentTicket.spamTicket || this.currentTicketCaseFiles.length > 0 || this.checkboxValue;
},
markSpam: {
get: function() {
return this.currentTicket.spamTicket
},
set: function(val) {
return this.updateTicket([this.currentTicket.id, { spam_ticket: val }]);
},
}
},
methods: {
...mapActions(['updateTicket']),
...mapServerActions(['createCaseFile', 'deleteCaseFile']),
cfBelongsToCurrentTicket(id){
return this.currentTicketCaseFiles.map((x) => x.caseFileId).includes(id);
},
cantAssignCaseFileCheckbox(isChecked){
if(isChecked) {
this.createCaseFile({ isCfNotAssociated: true });
} else {
this.deleteCaseFile(this.currentTicketCaseFileNotAssociated);
}
},
onFoundCaseFile(id, useIt){
console.log("onFoundCaseFile: ", id, useIt);
if(useIt) {
this.createCaseFile({ caseFileId: id });
} else {
this.deleteCaseFile(this.currentTicketCaseFiles.find({ caseFileId: id }));
}
},
searchCaseFiles() {
const newData = this.filter;
if (newData.length < 3) { // TODO: some smarter condition here
this.foundCaseFiles = [];
this.lookupStatus = 'waitingInput';
return;
}
this.lookupStatus = 'waitingServer';
this.$axios.get('case_files', { params: { "case_files.filter" : newData } })
.then((response) => {
this.foundCaseFiles = response.data.caseFilesSearchResult.caseFiles;
this.foundCaseFilesByParticipant = response.data.caseFilesSearchResult.foundByPrivateInfo;
if(this.foundCaseFiles.length > 0) {
this.lookupStatus = 'success';
} else {
this.lookupStatus = 'notFound';
}
}).catch(() => this.lookupStatus = 'error');
}
},
};
</script>
</style>
Add this to your TicketRunner.vue Component script:
computed: {
...mapGetters(['currentTicketCaseFiles'])
}

Quasar Framework with VueX

I am having a problem which I can't seem to solve. First some Code:
<template>
<div>
<q-input name="email" v-model="user.email" />
<q-input name="userName" v-model="user.userName" />
</div>
</template>
<script>
export default {
name: 'UserDetail',
props: {
id: String
},
computed: {
user: {
get () {
return this.$store.state.users.user
},
set (value) {
this.$store.dispatch('users/updateUser', this.id, value)
}
}
}
}
</script>
And here the store action:
export function updateUser ({ commit }, id, user) {
if (id !== user.id) {
console.log('Id und User.Id stimmen nicht überein')
} else {
api.put(`/User/${id}`, user).then(response => {
commit('upsertUser', response.data)
}).catch(error => {
console.log(error)
})
}
}
Mutation:
export const upsertUser = (state, user) => {
const idx = state.userList.findIndex(x => x.id === user.id)
if (idx !== -1) {
state.userList[idx] = user
} else {
state.userList.push(user)
}
if (state.user?.id === user.id) {
state.user = user
}
}
Now as far as I can tell this adheres to the vuex recommendet way of doing things like this (https://vuex.vuejs.org/guide/forms.html#two-way-computed-property)
But I always get the Error: do not mutate vuex store state outside mutation handlers
I can not figure out how to do this. Can someone point me in the right direction?
Dispatching Actions: it only allows one input parameter, from this point you can read the state but you cannot modify it
Mutations: it only allows one input parameter, from here you can read and write about the state, but if you want the variables that listen for the changes of the object to "listen for the changes" you have to use
Object.assign or Vue.set when using objects
I did not understand your data, so I leave you a similar solution, I hope it helps you
let users = {
namespaced: true,
state: {
userList: {
'ID43769906': {
id: 'ID43769906',
email: 'manueltemple#gmail.com',
username: 'mtemple'
}
}
},
mutations: {
upsertUser(state, payload) {
if (state.userList.hasOwnProperty(payload.id)) {
Object.assign(state.userList[payload.id], payload)
} else {
Vue.set(state.userList, payload.id, payload)
}
}
},
actions: {
updateUser({ commit }, payload) {
console.log('api put')
commit('upsertUser', payload)
/*
api.put(`/User/${id}`, user).then(response => {
commit('upsertUser', response.data)
}).catch(error => {
console.log(error)
})
*/
}
},
getters: {
getUser: (state) => (id) => {
let result = null
let data = state.userList || {}
Object.keys(data).forEach((key) => {
if (key === id) {
result = data[key]
return false
}
})
return result
}
}
}
const store = new Vuex.Store({
modules: {
users
},
})
//import { mapGetters } from 'vuex'
new Vue({
store,
el: '#q-app',
data: function () {
return {
id: 'ID43769906'
}
},
mounted () {
window._this = this
},
computed: {
...Vuex.mapGetters('users', ['getUser']),
user: {
get () {
return this.getUser(this.id)
// return _this.$store.getters['users/getUser'](this.id)
},
set (value) {
this.$store.dispatch('users/updateUser', value, { root: true })
}
}
}
})
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/#quasar/extras/material-icons/material-icons.css">
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/quasar/dist/quasar.min.css">
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script src="https://unpkg.com/vuex"></script>
<script src="https://cdn.jsdelivr.net/npm/quasar/dist/quasar.umd.min.js"></script>
<div id="q-app">
<div>
<q-input name="email" v-model="user.email" />
<q-input name="userName" v-model="user.username" />
</div>
</div>
https://jsfiddle.net/idkc/Lu21eqv9/46/

Vue - Loading dynamic drop down inside a v-for for editing

I have form which has a function of creating multiple fields and for each field it has dynamic drop down. When creating a new form, the dynamic drop down is working smoothly but when I try to edit the form which loads all the data, it's having a hard time loading the data for row.section_objective_id and row.section_kpa_id since it is reliable to it's v-for index.
corporate_objectives: [],
department_objectives: [],
tableItems {
rows: [],
}
<base-select
v-model="tableItem.corporate_objective_id"
:items="corporate_objectives"
item-text="name"
item-value="id"
#change="getDepartmentObjectives()"
/>
<v-btn #click="addKPA()"</v-btn>
<div v-for="(row, index) in tableItem.rows" :key="row.id">
<base-select
v-model="row.deparment_objective_id"
:items="department_objectives"
item-text="name"
item-value="id"
#change="getSectionObjectives(index)"
/>
<base-select
v-model="row.section_objective_id"
:items="row.section_objectives"
item-text="name"
item-value="id"
#change="getSectionKPA(index)"
/>
<base-select
v-model="row.section_kpa_id"
:items="row.section_kpas"
item-text="name"
item-value="id"
/>
</div>
created() {
this.getEmployeeObjectives()
}
methods: {
addKPA() {
this.tableItem.rows.unshift({
department_objective_id: '',
section_objectives: [],
section_objective_id: '',
section_kpas: [],
section_kpa_id: '',
})
},
async getEmployeeObjectives() {
try {
let res = await axios.get('/api/employee-objectives')
this.tableItem = await Object.assign({}, res.data)
this.getDepartmentObjectives()
} catch(error) {
console.log(error)
}
},
async getCorporateObjectives() {
try {
let res = await axios.get('/api/corporate-objectives')
this.corporate_objectives = _.orderBy(res.data, ['name'], ['asc'])
} catch(error) {
console.log(error)
}
},
async getDepartmentObjectives() {
try {
let res = await axios.get('/api/corporate-department-objective',
{ params: { corporate_objective_id: this.tableItem.corporate_objective_id }})
this.department_objectives = _.orderBy(res.data, ['name'], ['asc'])
} catch(error) {
console.log(error)
}
},
async getSectionObjectives(index) {
try {
let res = await axios.get('/api/department-section-objective',
{ params: { department_objective_id: this.tableItem.rows[index].department_objective_id }})
this.tableItem.rows[index].section_objectives = _.orderBy(res.data, ['name'], ['asc'])
} catch(error) {
console.log(error)
}
},
async getSectionKPA(index) {
try {
let res = await axios.get('/api/section-kpa',
{ params: { section_objective_id: this.tableItem.rows[index].section_objective_id }})
this.tableItem.rows[index].section_kpas = _.orderBy(res.data, ['name'], ['asc'])
} catch(error) {
console.log(error)
}
},
}

Function only gets called once in NuxtJs

I am using NuxtJs in my project, I a have list of checkboxes, on click of each checkbox I am sending an array of checkboxes to a my POST api which return data.
Here, when I check the first checkbox it returns the data. But when I check the second checkbox it does not does return the data.
I mean it only returns the data on single checkbox checked.
Its working with normal vuejs but not in nuxtjs
My Code:
<script>
import axios from "axios";
import uniq from "lodash/uniq";
export default {
async asyncData({ req, params }) {
let [storeInfo, feedsInfo] = await Promise.all([
axios.get(
process.env.apiURL +
"/stores/findOne?filter[where][store_name]" +
"=" +
params.id
),
axios.post(process.env.apiURL + "feeds/feedsByStores", {
stores: [params.id]
})
]);
return {
stores: storeInfo.data,
feeds: feedsInfo.data,
categories: uniq(feedsInfo.data.map(p => p.feed_category))
};
},
data() {
return {
checkedCategories: [],
checkedCategory: false,
selectedCategories: []
};
},
methods: {
feedsByCategories: function(categories) {
console.log(categories);
axios.post(process.env.apiURL + "feeds/feedsByCategories", {
categories: [categories]
}).then((res) => {
console.log(res);
})
},
categoryChecked: function(category, checked) {
this.display = "inline";
if (checked) {
this.selectedCategories.push(category);
console.log(this.selectedCategories);
this.feedsByCategories(this.selectedCategories);
} else if (!checked) {
const index = this.selectedCategories.indexOf(category);
this.selectedCategories.splice(index, 1);
this.feedsByCategories(this.selectedCategories);
if (this.selectedCategories == "") {
this.display = "none";
this.getFeeds();
}
}
if (!checked && this.selectedCategories.length === 0) {
this.getFeeds();
}
},
uncheckCategory: function(checkedCategory) {
this.checkedCategories = this.checkedCategories.filter(
name => name !== checkedCategory
);
const index = this.selectedCategories.indexOf(checkedCategory);
this.selectedCategories.splice(index, 1);
this.feedsByCategories(this.selectedCategories);
if (this.checkedCategories == "") {
this.display = "none";
this.getFeeds();
}
},
uncheckallCategories: function(event) {
this.checkedCategories = [];
this.display = "none";
this.search = "";
this.Search = "";
this.filteredCategories;
},
getFeeds() {
return this.feeds;
}
}
};
</script>
<template>
<v-layout>
<ul class="list-unstyled scrollbar">
<li v-for="(feedcategory, index) in categories" :key="feedcategory.id">
<input type="checkbox" name="category" #change="categoryChecked(feedcategory,$event.target.checked)"
:id="index + 1" :value="feedcategory" v-model="checkedCategories">
{{ feedcategory }}
</li>
</ul>
</v-layout>
</template>
My Typo,
I removed the brackets for my categories array and it worked:
feedsByCategories: function(categories) {
console.log(categories);
axios.post(process.env.apiURL + "feeds/feedsByCategories", {
categories: categories
}).then((res) => {
console.log(res);
})
}