Function only gets called once in NuxtJs - vue.js

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);
})
}

Related

v-for doesn't rerender after data is updated and needs page refresh to see the change

In Vue.js 2, I'm using Axios and I can't render the data without refreshing the page, can you help me on what to do?
<div v-for="task in tasks" :key="task.id" class="title">
{{ task.title }}
</div>
export default {
name: "TodoList",
data() {
return {
tasks: [],
newTask: "",
taskId: null,
}
},
methods: {
async addTask() {
this.taskId = this.tasks.length + 1
if (this.newTask.trim().length === 0) {
return
}
const task = {id: this.taskId, title: this.newTask}
const created = await API.createTasks(task)
this.tasks.push(created)
this.newTask = ""
}
},
async created() {
this.tasks = await API.getTasks();
}
}
// API.js
export default {
async createTasks(task) {
return axios.post(this.withPath('/api/v1/tasks'),task).then(r => r.data)
},
async getTasks() {
return axios.get(this.withPath('/api/v1/tasks')).then(r => r.data)
},
}
This is my response body of POST:
{"id":1,"title":"buy a water"}

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'])
}

Don't send Axios request when user selects a result

I'm using v-autocomplete from vuetify.js to retrieve a list of values from API Server.
It works fine and my list of values is not empty.
But my problem is when I select the correct value from this list. My script sends another request to server to retrieve another autocomplete list.
Do you have any idea to avoid to send request when a result is selected by the user ? Or to send request only when a key is down ?
My component :
<template>
<div>
<v-autocomplete
v-model="selectValeur"
:loading="loading"
:search-input.sync="search"
:items="resultatsAutocomplete"
class="mb-4"
hide-no-data
hide-details
:label="recherche.label"
></v-autocomplete>
</div>
</template>
<script>
export default {
props: {
recherche: {
type: Object,
default: null,
},
},
data: () => ({
selectValeur: null,
loading: false,
search: null,
resultatsAutocomplete: [],
}),
watch: {
selectValeur(oldval, val) {
console.log(oldval)
console.log(val)
},
search(val) {
val && val !== this.selectValeur && this.fetchEntriesDebounced(val)
console.log(val)
if (!val) {
this.resultatsAutocomplete = []
}
},
},
methods: {
fetchEntriesDebounced(val) {
// cancel pending call
clearTimeout(this._timerId)
// delay new call 500ms
this._timerId = setTimeout(() => {
this.querySelections(val)
}, 500)
},
async querySelections(v) {
if (v.length > 1) {
this.loading = true
try {
const result = await this.$axios.$get(
'myapi/myurl',
{
params: {
racine: v,
},
}
)
this.resultatsAutocomplete = result
console.log(this.resultatsAutocomplete)
this.loading = false
} catch (err) {
console.log(err)
this.loading = false
}
} else {
this.resultatsAutocomplete = []
}
},
},
}
</script>
Thanks,
selectValeur would no longer be null if the user has selected a value, so you could update search() to return if selectValeur is truthy:
export default {
watch: {
search(val) {
if (this.selectValeur) {
// value already selected
return
}
//...
}
}
}
Or you could use vm.$watch on the search property to be able to stop the watcher when selectValeur is set:
export default {
mounted() {
this._unwatchSearch = this.$watch('search', val => {
val && val !== this.selectValeur && this.fetchEntriesDebounced(val)
if (!val) {
this.resultatsAutocomplete = []
}
})
},
watch: {
selectValeur(val) {
if (val && this._unwatchSearch) {
this._unwatchSearch()
}
}
}
}
I found a solution to my problem.
I used the #keyup event to send the axios request and I deleted the watcher on search.
So, the API request are only sent when I press a key.
<template>
<div>
<v-autocomplete
v-model="selectValeur"
:loading="loading"
:items="resultatsAutocomplete"
:search-input.sync="search"
class="mb-4"
hide-no-data
hide-details
:label="recherche.label"
#keyup="keyupSearch"
></v-autocomplete>
</div>
</template>
<script>
export default {
props: {
recherche: {
type: Object,
default: null,
},
},
data: () => ({
selectValeur: null,
loading: false,
resultatsAutocomplete: [],
search: '',
}),
methods: {
keyupSearch(val) {
val &&
val !== this.selectValeur &&
this.fetchEntriesDebounced(this.search)
if (!val) {
this.resultatsAutocomplete = []
}
},
fetchEntriesDebounced(val) {
// cancel pending call
clearTimeout(this._timerId)
// delay new call 500ms
this._timerId = setTimeout(() => {
this.querySelections(val)
}, 500)
},
async querySelections(v) {
if (v.length > 1) {
this.loading = true
try {
const result = await this.$axios.$get(
'my-api/my-url',
{
params: {
sid: this.$route.params.sid,
service: this.$route.params.service,
type: this.recherche.mode,
racine: v,
},
}
)
this.resultatsAutocomplete = result
console.log(this.resultatsAutocomplete)
this.loading = false
} catch (err) {
console.log(err)
this.loading = false
}
} else {
this.resultatsAutocomplete = []
}
},
},
}
</script>

Live Search Component

I am fetching my data from external API link and I would like to filter them - live searching, so when I type a letter, the results that includes that letter will get filtered.
For now I am not able to do it and when I click into my input I just get all the results printed out and nothing is happening when I am trying to filter them.
This is my logic in script:
data() {
return {
result: "",
modal: false,
results: [],
filteredResults: [],
};
},
mounted() {
axios
.get("secretDataURL")
.then((response) => {
this.filteredResults = response.data;
})
.catch((error) => (this.filteredResults = console.log(error)))
.finally(() => console.log("Data successfully loaded"));
},
methods: {
filterResults() {
if (this.result.lenght == 0) {
this.filteredResults = this.results;
}
this.filteredResults = this.results.filter((result) => {
return result.toLowerCase().startsWith(this.result.toLowerCase());
});
},
setResult(result) {
this.result = result;
this.modal = false;
},
},
watch: {
state() {
this.filterResults();
},
}
And my template
<div #click="modal = false"></div>
<input
type="text"
v-model="result"
autocomplete="off"
#input="filterResults"
#focus="modal = true"
/>
<div v-if="filteredResults && modal">
<ul>
<li
v-for="(filteredResult, index) in filteredResults"
:key="index"
#click="setResult(filteredResult)"
>
{{ filteredResult.name }}
</li>
</ul>
</div>
How can I make it work, where my logic is failing ?
U get the data in hook mounted.
And then lost it.
Change this.filteredResults = response.data; to this.results = response.data;

Vue-Apollo and query autocomplete

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(){
...
},
}