RROR Error: Cannot find control with path: 'IncomeList -> verified' - angular8

I am creating dynamic reactive form using Angular 10, it has the nested structure, when I try to bind the form to the template I have the below errors
**The main form has below structure
main form --> formArray--> formGroup --> controls/FormGroup
How do I bind this nested structure in HTML form?**
>its a dynamic form
```buildForms() {
const incomeList: FormArray = this.fb.array([]);
this.declaredFinancialInfo.Income.forEach((item: Income2, index: number) => {
incomeList.push(this.createIncomeForm(item,
this.verifiedFinancialInfo.Income[index]));
});
this.applicantForm = this.fb.group({
DeclaredAnnualIncome: new
FormControl(this.verifiedFinancialInfo?.DeclaredAnnualIncome || null, []),
IncomeList: incomeList,
});
createIncomeForm(declared: Income2, verified: Income2): FormGroup {
const incomeForm = this.fb.group({});
const verifiedIncomeForm = this.fb.group({});
for (let key in verified) {
if (key === 'Source') {
verifiedIncomeForm.addControl(key, new FormControl(verified[key] || '',
[Validators.required]));
} else if (key === 'OfficialMnthIncome') {
verifiedIncomeForm.addControl(key, new FormControl(verified[key] || null,
[Validators.required]));
} else if (key === 'GreyMnthIncome') {
verifiedIncomeForm.addControl(key, new FormControl(verified[key] || null,
[]));
} else if (key === 'MaternityEndDate') {
verifiedIncomeForm.addControl(key, new FormControl(verified[key] || new
Date(), []));
} else if (key === 'BusinessIncome') {
} else {
verifiedIncomeForm.addControl(key, new FormControl(verified[key]));
}
}
incomeForm.addControl('verified', verifiedIncomeForm);
return incomeForm;
}
```
<form [formGroup]="applicantForm">
<div *ngFor="let alias of incomeLIstArray.controls; let i=index">
<div formArrayName="IncomeList">
<div class="form-group" *ngFor="let _verified of alias?.controls?.verified?.controls | keyvalue; let i = index;">
<div formGroupName="verified">
{{ _verified.key }}
<select class="form-control"
[ngClass]="{'submitted': isSubmit}" formControlName="{{_verified.key}}" name="{{_verified.key}}">
<option value="">-{{'lbl_SelectOption' | translate}}</option>
<option *ngFor="let item of dropdowns?.CB_IncomeSource?.Item;"
[value]="item.Value">{{item.Description}}</option>
</select>
</div>
</div>
</div>
</div>
</form>```
The above code throws the below error
core.js:4442 ERROR Error: Cannot find control with path: 'IncomeList -> verified'
core.js:4442 ERROR Error: Cannot find control with path: 'IncomeList -> verified -> Currency'

Related

Fill vuetify combobox with data from function

I have a Vuetiy combobox that's part of a form in which the user can search voor data by email and it returns a object (a visitor):
<EmailInput
v-model="selectedVisitor"
:required="true"
:autocomplete="true"
:items="visitors"
/>
<template>
<div>
<v-text-field
v-if="!autocomplete"
:rules="[
(v) => !!v || $t('error.emailRequired'),
(v) => /.+#.+\..+/.test(v) || $t('error.emailInvalid'),
]"
outlined
:label="label"
required
#input="$emit('input', $event)"
/>
<v-combobox
v-if="autocomplete"
:rules="[
(v) => !!v || $t('error.emailRequired'),
]"
:label="label"
outlined
append-icon="arrow_drop_down"
:items="items"
item-text="email"
item-value="email"
#input="$emit('input', $event)"
>
<template v-slot:selection="data">
{{ data.item.email || data.item }}
</template>
<template v-slot:item="data">
{{ data.item.name }} - {{ data.item.email }}
</template>
</v-combobox>
</div>
</template>
<script>
export default {
props: {
required: Boolean,
items: Array,
autocomplete: Boolean,
},
computed: {
label() {
if (this.required === true) {
return this.$i18n.t('labels.emailReq');
}
return this.$i18n.t('labels.email');
},
},
};
</script>
If an existing visitor is found, the other form fields are filled, otherwise just the email is sent to the newVisitor object.
selectedVisitor(val) {
if (typeof val === 'object' && val !== null) {
this.newAppointment.visitorId = val.id;
this.existingVisitor = true; // we don't have to write it to the db
Object.assign(this.newVisitor, val); // for displaying the data only, object assign because db array was edited too when data was modified or deleted
} else if (val === null || val === '') {
this.newAppointment.visitorId = '';
this.newVisitor = {};
} else {
this.newVisitor.email = val;
}
},
The user then clicks a button and the data gets sent to the multipleVisitors array.
The problem is with the function that repopulates these form fields if the user wants to edit the data:
editVisitor(email) {
// get the visitor that needs to be edited
const visitorArray = this.multipleVisitors.filter((el) => el.email === email);
const visitor = visitorArray[0];
// set the visitor to the form
this.newVisitor = visitor;
// variable set to combobox
this.selectedVisitor = visitor.email;
// remove the visitor from the visitor array
this.multipleVisitors = this.multipleVisitors.filter((el) => el.email !== email);
},
The form is filled in except for the combobox which stays empty. If I look at vue dev tools the variable assigned to the v-model does contain the email address.
I've tried assigning the whole object to selectedVisitor but that does not change anything.
this could maybe help
<v-combobox :items="MyFillFuntion()"></v-combobox>
<v-combobox :items="MyFillFuntion(scopeData)" # optional></v-combobox>
and don't forget
<script>
export default {
data: () => ({
MyFillFuntion() {
let array_i_need_to_fill = [];
// your process ... ...
// and finally
return array_i_need_to_fill;
}
}),
props: {
required: Boolean,
items: Array,
autocomplete: Boolean,
},
computed: {
label() {
if (this.required === true) {
return this.$i18n.t('labels.emailReq');
}
return this.$i18n.t('labels.email');
},
},
};
</script>

Error mesage quickly displaying then hiding as expected in VUE

I have a page which I validate the email add input #blur. This works perfectly and displays the error message if it fails validation rules set but the issue I have is that due to the #blur, when I click my reset button the error quickly displays then hides and this is poor UI and I want to stop it but can't figure out how to.
HTML
<div class="card" v-on:click="select($event)">
<div class="card-body">
<div class="form-group row">
<label class="col-sm-3 col-form-label pr-0" for="emailAddField">Email <span class="text-danger">*</span></label>
<div class="col-sm-9">
<div class="input-group">
<input id="emailAddField" ref="pdEmailAdd" class="form-control" type="search" :value="pdEmailAdd" #input="pdEmailAddInput" #blur="emailInputValChecker($event)" placeholder="Enter an email address">
<div class="input-group-append" :class="emailButtonValidation">
<a class="btn input-group-text primaryBtn" :class="emailButtonValidation" type="button" :href="'mailto:' + pdEmailAdd">
<i class="far fa-envelope"></i>
</a>
</div>
</div>
<div v-if="emailFormatErrorMsg" class="text-danger">Incorrect email address format</div>
</div>
<div class="card-footer">
<button id="resetButton" ref="resetButton" class="btn btn-warning col-4" #click="pdInitPageStates($event)" :disabled="resetDetails">
Reset details
</button>
</div>
</div>
I have 'hacked' at the card trying to use #click on the card to get the id but this didn't work so I set the id in my `data but not happy about it and sure there is a lot better way but I just can't figure it out
Code
data() {
return {
pdEmailAdd: '',
reg: /^(([^<>()\[\]\\.,;:\s#"]+(\.[^<>()\[\]\\.,;:\s#"]+)*)|(".+"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,24}))$/,
detailsChanged: false,
emailIncorrectFormat: false,
targetId: 'resetButton', // HACK
targetId2: '', // HACK
}
},
computed: {
emailButtonValidation() {
if (!this.pdEmailAdd || !this.reg.test(this.pdEmailAdd)) {
if (this.pdEmailAdd === '') {
this.emailIncorrectFormat = false;
} else {
this.emailIncorrectFormat = true;
}
return 'disabled'
} else {
this.emailIncorrectFormat = false;
return ''
}
},
resetDetails() {
this.detailsChanged = false;
if (this.pdName != this.$store.state.account.firstname + ' ' + this.$store.state.account.lastname) {
this.detailsChanged = true;
}
if (this.telNoType === 'ddi' && this.pdTelNo != this.$store.state.account.ddi) {
this.detailsChanged = true;
} else if (this.telNoType === 'mobile' && this.pdTelNo != this.$store.state.account.mobile) {
this.detailsChanged = true;
} else if (this.telNoType === 'na' && this.pdTelNo != '') {
this.detailsChanged = true;
}
if (this.pdExtNo != this.$store.state.account.extension) {
this.detailsChanged = true;
}
if (this.pdEmailAdd != this.$store.state.user.adminemail) {
this.detailsChanged = true;
}
return !this.detailsChanged;
}
}
// Another hack to try set it soon as page loads
mounted() {
this.$refs.resetButton.click();
},
methods: {
emailInputValChecker(event) {
this.emailFormatErrorMsg = false;
if (!this.pdEmailAdd || !this.reg.test(this.pdEmailAdd)) {
if (this.pdEmailAdd === '') {
this.emailFormatErrorMsg = false;
} else {
this.select(event)
// Uses the 'dirty hacks'
if (this.targetId !== '' && this.targetId !== 'resetButton' && this.targetId2 !== 'resetButton') {
this.emailFormatErrorMsg = true;
};
}
}
},
select(event) {
this.targetId = event.target.id;
if (this.targetId === 'resetButton') {
this.targetId2 === 'resetButton';
} else if (this.targetId === '') {
this.targetId === 'resetButton';
}
}
}
Basically all I want is the input to check it passes validation when input is left unless the reset button is clicked then ignore it but think I've gone code blind and can't think of a way to do this.
The mousedown event on your reset button is what causes blur on the input to fire. Adding #mousedown.prevent to the reset button will stop that from happening specifically when the reset button is clicked.
This snippet ought to illustrate the solution. Remove #mousedown.prevent from the reset button and you'll see similar behavior to your issue, with the error briefly flashing.
new Vue({
el: '#app',
data: {
email: '',
error: null
},
methods: {
onBlur () {
if (this.email === 'bad') {
this.error = 'bad email!'
} else {
this.error = null
}
},
onReset () {
this.error = null
this.email = ''
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<button #click="onReset" #mousedown.prevent>Reset</button>
<p>
Type in "bad" and leave input to trigger validation error<br>
<input type="text" v-model="email" #blur="onBlur"/>
</p>
<p>{{ email }}</p>
<p v-if="error">error!</p>
</div>

Smart filter in Bootstrap Vue

I was wondering if there is any way to implement the Intelligent DataTables filter in the Bootstrap-Vue table, I have searched everywhere, but I have not found any functional solution to implement in my project.
DataTable.net smart filter image
We needed that component so we created it. Hope it helps:
SearchTable.vue
<template>
<div class="search-table h-100 justify-content-center align-items-center"
v-bind:class="{row: data.length === 0}" v-if="isMounted">
<div v-if="data.length > 0">
<div class="d-flex justify-content-between">
<!-- main search -->
<b-input-group size="xs">
<b-form-input v-model="searchInput"></b-form-input>
<b-input-group-append is-text>
<b-icon icon="search"></b-icon>
</b-input-group-append>
</b-input-group>
</div>
<div class="d-flex justify-content-between mt-2 mb-0">
<b-button-group>
<!-- dropdown -->
<b-dropdown id="col-dropdown" class="col-dropdown" no-flip text="Visibilité">
<b-dropdown-item :key="field.key" class="p-0" style="padding: 0" v-for="field in fields"
v-if="field.key !== 'action'">
<div #click.stop="onDropdownClick(field.key)"
class="checkbox-wrapper">
<b-form-checkbox
:checked="isColumnDisplayed(field.key)"
disabled
>
{{ field.label || field.key }}
</b-form-checkbox>
</div>
</b-dropdown-item>
</b-dropdown>
<b-button :variant="noneOfSearchMethodIsUsed ? '' : 'danger'" #click="cancelFilters">Enlever filtre</b-button>
<!-- dropdown action groupées -->
<slot name="groupped-actions"></slot>
</b-button-group>
<div align="right" style="display: inline-flex">
<span style="margin: 4px;">Afficher</span>
<b-form-select
v-model="perPage"
:options="perPageOptions"
size="sm"
></b-form-select>
<span style="margin: 4px;">éléments</span>
</div>
</div>
<div class="d-flex justify-content-between mt-0 mb-2">
<span style="margin-top: 5px;">{{ buildInformationLine }}</span>
<!-- pagination -->
<b-pagination
:per-page="perPage"
:total-rows="formattedData.length"
align="right"
class="my-0 mt-1"
size="sm"
v-model="currentPage"
></b-pagination>
</div>
<!-- TABLE -->
<b-table
:current-page="currentPage"
:fields="fieldsToShow"
:items="formattedData"
:per-page="perPage"
foot-clone
no-footer-sorting
primary-key="id"
:sticky-header="true"
responsive
striped
>
<!-- action col template -->
<template
v-if="!!$scopedSlots.action"
v-slot:cell(action)="row">
<slot name="action" v-bind="row.item"></slot>
</template>
<!-- html escape template -->
<template v-slot:cell()="data">
<span v-html="data.value"></span>
</template>
<!-- footer -->
<template v-slot:foot()="data">
<input :value="getFieldFromKey(data.column).searchVal"
#input="setFieldSearchValue(data.column, $event.target.value)"
v-if="getFieldFromKey(data.column).key !== 'action'"
class="w-100"
placeholder="Recherche">
</template>
</b-table>
<div class="d-flex justify-content-between mt-0">
<span style="margin-top: 5px;">{{ buildInformationLine }}</span>
<!-- pagination -->
<b-pagination
:per-page="perPage"
:total-rows="formattedData.length"
align="right"
class="my-0 mt-1"
size="sm"
v-model="currentPage"
></b-pagination>
</div>
</div>
<div v-else>
<p>Aucun résultat</p>
</div>
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator'
import BvTableField from '../../interfaces/BvTableField'
enum SearchFusionMethod {
Union = 'union',
Intersection = 'intersection',
}
interface FieldsInteractiveInterface extends BvTableField {
searchVal: string
stickyColumn: boolean
}
#Component
export default class SearchTable extends Vue {
// The array containing the data objects
#Prop(Array) readonly data!: any[]
// The array containing the info of each column. key must be equal to key in object data
#Prop(Array) readonly fields!: BvTableField[]
#Prop({default: SearchFusionMethod.Intersection}) readonly searchFusionMethod!: SearchFusionMethod
#Prop({default: 'highlight'}) readonly highlighterClass!: string
mainHighlighterClass: string = this.highlighterClass
#Prop({default: 'field-highlight'}) readonly fieldHighlighterClass!: string
currentPage = 1
perPage = 10
perPageOptions = [10, 25, 50, 100]
searchInput = ''
isMounted = false
// Contains the value of each column search field
fieldsInteractive: FieldsInteractiveInterface[] = []
// ---
mainHilightColor: string = 'yellow'
fieldHilightColor: string = 'orange'
get fieldsToShow(): BvTableField[] {
return this.fieldsInteractive.filter(field => {
return field.display
})
}
get noneColumnSearchFieldIsUsed(): boolean {
return this.numberOfSearchFieldsUsed === 0
}
get numberOfSearchFieldsUsed(): number {
return this.fieldsInteractive.reduce((count: number, field) => {
return count + (field.searchVal !== '' ? 1 : 0)
}, 0)
}
// (01), (10)
get exactlyOneSearchMethodIsUsed(): boolean {
return (this.searchInput !== '' && this.noneColumnSearchFieldIsUsed) || (this.searchInput === '' && !this.noneColumnSearchFieldIsUsed)
}
// (00)
get noneOfSearchMethodIsUsed(): boolean {
return (this.searchInput === '' && this.noneColumnSearchFieldIsUsed)
}
// (11)
get bothSearchMethodsAreUsed(): boolean {
return (this.searchInput !== '' && !this.noneColumnSearchFieldIsUsed)
}
get onlyMainSearchIsUsed(): boolean {
return (this.searchInput !== '' && this.noneColumnSearchFieldIsUsed)
}
get onlyFieldSearchIsUsed(): boolean {
return (this.searchInput === '' && !this.noneColumnSearchFieldIsUsed)
}
get buildInformationLine(): string {
const txt: String[] = []
txt.push("Affichage de l'élément")
txt.push(this.formattedData.length === 0 ? '0' : (((this.currentPage-1) * this.perPage)+1).toString())
txt.push('à')
txt.push((this.currentPage * this.perPage < this.formattedData.length ? this.currentPage * this.perPage : this.formattedData.length).toString())
txt.push('sur')
txt.push((this.formattedData.length).toString())
txt.push('éléments')
if (this.formattedData.length < this.data.length) {
txt.push('(filtré de')
txt.push((this.data.length).toString())
txt.push('éléments au total)')
}
return txt.join(' ')
}
// Data with
get formattedData() {
const mapped = this.data
.map((item: any) => {
const itemWithHighlight: any = {}
this.fields.forEach(field => {
itemWithHighlight[field.key] = this.replaceBySearch(field.key, item[field.key])
})
return itemWithHighlight
})
return mapped
.filter((item: any) => {
// (searchInput,columnSearchField)
// If there is no filter at all, return the row (00)
if (this.noneOfSearchMethodIsUsed) return true
let countFromMainHighlight = 0
let countFromFieldHighlight = 0
// loop through each field
for (const [key, col] of Object.entries(item)) {
if (!this.fieldsInteractive[this.fieldsInteractive.findIndex(x => x.key === key)].display) continue // Only search in displayed column
if (typeof col !== 'string') continue // only check in string values
if (this.onlyMainSearchIsUsed) {
// if only one of the search method has been used, return anything having a 'highlight' class (01), (10)
if (col.includes('fromMainSearch') || col.includes(this.fieldHighlighterClass)) {
return true
}
} else {
// if both of the search method have been used, filter according to the searchFusionMethod (11)
if (this.searchFusionMethod === SearchFusionMethod.Intersection) {
// TODO: search only in class attribute of markup (faster)
if (col.includes('fromMainSearch')) {
countFromMainHighlight++
}
if (col.includes('fromFieldSearch')) {
countFromFieldHighlight++
}
} else if (this.searchFusionMethod === SearchFusionMethod.Union) {
if (col.includes(`<span class="${this.highlighterClass}`)) {
// TODO
return true
}
}
}
}
// determine whether we keep the row
if (this.bothSearchMethodsAreUsed) {
return countFromMainHighlight > 0 && countFromFieldHighlight === this.numberOfSearchFieldsUsed
} else {
if (this.onlyMainSearchIsUsed) {
return countFromFieldHighlight > 0
} else if (this.onlyFieldSearchIsUsed) {
return countFromFieldHighlight === this.numberOfSearchFieldsUsed
}
}
})
}
isColumnDisplayed(key: string) {
const field = this.getFieldFromKey(key)
return field.display
}
setFieldSearchValue(key: string, searchVal: string) {
const index = this.fieldsInteractive.findIndex(field => field.key === key)
if (index === -1) throw new DOMException('Key not found')
Vue.set(this.fieldsInteractive, index, {
...this.fieldsInteractive[index],
searchVal: searchVal
})
// this.fieldsInteractive[index].searchVal = searchVal
}
mounted() {
// programatically add action column if slot given
if (!!this.$scopedSlots.action) {
const fieldAction = {key: 'action'}
this.fields.push(fieldAction)
}
// init column search values
this.fields.forEach(field => {
if (field.key === 'action') {
this.fieldsInteractive.unshift({
...field,
searchVal: '',
sortable: false,
display: field.display ?? true,
stickyColumn: true
})
} else {
this.fieldsInteractive.push({
...field,
searchVal: '',
sortable: field.sortable ?? true,
display: field.display ?? true,
stickyColumn: false
})
}
})
this.isMounted = true
}
onDropdownClick(key: string) {
for (const index in this.fieldsInteractive) {
if (this.fieldsInteractive[index].key === key) {
this.fieldsInteractive[index].display = !this.fieldsInteractive[index].display // toggle
return
}
}
}
private cancelFilters(): void {
this.fieldsInteractive = this.fieldsInteractive.map((field) => {
field.searchVal = ''
return field
})
this.searchInput = ''
}
private getFieldFromKey(key: string): FieldsInteractiveInterface {
const f = this.fieldsInteractive.find(field => field.key === key)
if (f === undefined) {
throw new DOMException('Key not found')
}
return f
}
private replaceBySearch(key: string, str: string | any) {
if ((this.searchInput === '' && this.noneColumnSearchFieldIsUsed)
|| str === undefined || str === null) return str
str = String(str)
// main search bar
if (this.exactlyOneSearchMethodIsUsed || this.bothSearchMethodsAreUsed) {
const regexMain: RegExp | undefined = this.searchInput !== '' ? new RegExp(`${this.searchInput}`, 'i') : undefined
const regexField: RegExp | undefined = this.getFieldFromKey(key).searchVal !== '' ? new RegExp(`${this.getFieldFromKey(key).searchVal}`, 'i') : undefined
const matchMain: string[] | null = regexMain ? (str).match(regexMain) : null
const matchField: string[] | null = regexField ? (str).match(regexField) : null
if (matchMain || matchField) {
str = this.surroundWithHilightClass(str, matchMain, matchField)
}
}
return str
}
// https://stackoverflow.com/questions/1144783/how-can-i-replace-all-occurrences-of-a-string
// replace only if not already contains a highlight class
/**
* #param str string to be surrounded
* #param findMain what is matching with main search
* #param findField what is matching with field search
*/
private surroundWithHilightClass(str: string, findMain: string[] | null, findField: string[] | null) {
const main: string | null = findMain && findMain.length > 0 ? findMain[0] : null
const field: string | null = findField && findField.length > 0 ? findField[0] : null
str = String(str)
// if a search is in another search, put two classes
if (field && main?.includes(field)) {
str = str.replace(new RegExp(main, 'g'), `<span class="${this.mainHighlighterClass} fromFieldSearch fromMainSearch">${main}</span>`)
} else if (main && field?.includes(main)) {
str = str.replace(new RegExp(field, 'g'), `<span class="${this.mainHighlighterClass} fromMainSearch fromFieldSearch">${field}</span>`)
} else {
// here we are sur the highlightning will be separated (this prevents having span in span)
if (main) {
str = str.replace(new RegExp(main, 'g'), `<span class="${this.mainHighlighterClass} fromMainSearch">${main}</span>`)
}
if (field) {
str = str.replace(new RegExp(field, 'g'), `<span class="${this.fieldHighlighterClass} fromFieldSearch">${field}</span>`)
}
}
return str
}
}
</script>
<style lang="scss">
.search-table {
div {
p {
color: gray;
text-align: center;
}
}
span.fromFieldSearch {
background-color: orange; // not defined : var(--main-highlighter-class);
}
/* Why this overrides fromFielSearch even if fromFieldSearch appear after in class order ? */
span.fromMainSearch {
background-color: yellow; // not defined : var(--field-highlighter-class);
}
span.field-highlight {
background-color: orange;
}
.col-dropdown {
.dropdown-item {
padding: 0 !important;
}
}
.checkbox-wrapper {
padding: 4px 24px;
width: 100%;
}
.custom-control-input[disabled] ~ .custom-control-label, .custom-control-input:disabled ~ .custom-control-label {
color: #000 !important;
}
.b-table-sticky-header > .table.b-table > thead > tr > th {
top: -2px !important;
}
.b-table-sticky-header {
max-height: calc(125vh - 400px) !important;
}
.b-table-sticky-header > .table.b-table > tfoot > tr > th {
position: sticky;
bottom: 0;
background-color: white;
z-index: 0;
}
th.b-table-sticky-column {
z-index: 4 !important;
}
}
</style>
The code is a bit messy but it works.
Note: we use vue class component with vuw property decorators

Filtering with Vuejs w/ axios

Hoping someone can point me in the right direction - I'm fairly new to Vuejs and have taken over someone else's code, although I've managed to do a fair bit without any issues, i'm struggling to get the filtering to work.
Here is the majority of the code:
<template>
<div v-if="showProperties">
<div v-if="properties.length > 0">
<div class="row">
<div class="col-2 listings--filter">
<select v-model="filter" class="form-control listings--filter_select">
<option value="" disabled hidden>Filter</option>
<option value="price-lowest">Lowest Price First</option>
<option value="price-highest">Highest Price First</option>
<option value="listing-new">Newest Listing First</option>
<option value="listing-old">Oldest Listing First</option>
</select>
</div>
<div class="col-2 listings--filtersstc">
<toggle-button v-model="sstcBtn"
:value="true"
:labels="{checked: 'Show SSTC', unchecked: 'Hide SSTC'}"
:width="120"
:height="40"/>
</div>
</div>
<div class="row margin-bottom">
<div class="col listings--filter">
<small v-show="showRemoveFilter">Current filter: {{ selectedFilter }}</small>
</div>
<div class="col listings--filter">
<small v-show="showRemoveFilter" v-on:click="removeSortProperties" class="float-right">Remove filter</small>
</div>
</div>
<div class="row">
<property-listings v-for="(property, index) in properties" v-bind:property="property" v-bind:key="index"></property-listings>
</div>
</div>
<div v-else>
<div class="row">
<div class="col text-center">
<p class="dark-text">No properties match your search criteria. Please try again.</p>
</div>
</div>
</div>
</div>
</template>
<script>
import PropertyListings from './PropertyListings.vue'
import * as VueGoogleMaps from 'vue2-google-maps'
import ToggleButton from 'vue-js-toggle-button'
Vue.use(ToggleButton)
Vue.use(VueGoogleMaps, {
load: {
key: '-------------------',
libraries: 'places', // This is required if you use the Autocomplete plugin
// OR: libraries: 'places,drawing'
// OR: libraries: 'places,drawing,visualization'
// (as you require)
}
})
export default {
data () {
return {
showProperties: false,
properties: [],
filter: '',
sstcBtn: true,
selectedFilter: '',
showRemoveFilter: false,
center: {lat: 00000, lng: -000000},
propertyInfoName: '',
propertyInfoPrice: '',
propertyInfoLink: '',
propertyInfoType: '',
infoWindowPos: {
lat: 0,
lng: 0
},
infoWinOpen: false,
currentMidx: null
}
},
delimiters: ['<%', '%>'],
components: {
PropertyListings
},
watch: {
filter: function(newFilter, oldFilter) {
this.sortProperties(newFilter);
},
sstcBtn: function(newSstcBtn, oldSstcBtn) {
this.removeSstcProperties(newSstcBtn);
}
},
methods: {
sortProperties: _.debounce(
function(newFilter) {
if(newFilter === 'price-highest') {
this.properties = _.orderBy(this.properties, 'price', 'desc')
this.selectedFilter = 'Highest Price First'
} else if(newFilter === 'price-lowest') {
this.properties = _.orderBy(this.properties, 'price', 'asc')
this.selectedFilter = 'Lowest Price First'
} else if(newFilter === 'listing-new') {
this.properties = _.orderBy(this.properties, 'id', 'desc')
this.selectedFilter = 'Newest Listing First'
} else if(newFilter === 'listing-old') {
this.properties = _.orderBy(this.properties, 'id', 'asc')
this.selectedFilter = 'Oldest Listing First'
}
this.showRemoveFilter = true
},
500
),
removeSstcProperties: _.debounce(
function(newSstcBtn) {
if(newSstcBtn == true){
console.log('do not filter')
//this.properties = _.filterBy(this.properties, 'status_id', '1')
return this.properties.filter(function(property){
return property.status_id == "1"
}.bind(this))
} else if(newSstcBtn == false){
console.log('do filter')
}
},
500
),
removeSortProperties: function() {
this.properties = _.orderBy(this.properties, 'id', 'desc')
this.showRemoveFilter = false
},
toggleInfoWindow: function(property, idx) {
this.infoWindowPos = {lat: Number(property.latitude), lng: Number(property.longitude)}
this.propertyInfoName = property.display_address
this.propertyInfoPrice = property.price
if(property.let_type_id === '3') {
this.propertyInfoLink = '/properties/for-students/' + property.main_searchable_areas + '/' + property.slug + '/' + property.property_ref
} else if(jQuery.inArray(property.let_type_id, ['1','2']) != '-1'){
this.propertyInfoLink = '/properties/for-rent/' + property.main_searchable_areas + '/' + property.slug + '/' + property.property_ref
} else {
this.propertyInfoLink = '/properties/for-sale/' + property.main_searchable_areas + '/' + property.slug + '/' + property.property_ref
}
}
},
created() {
var self = this;
axios.get('/properties/json')
.then(function (response) {
self.properties = response.data
self.showProperties = true
})
.catch(function (error) {
console.log(error);
});
}
}
</script>
Everything else is working fine but the bit i've been working on is the removeSstcProperties method, I've got the method triggering fine, however I'm struggling to actually filter the results for properties that have the status_id = 1. I've tried 2 ways (one commented out). Can anyone point me in the right direction?

Making a value from a fetch call available immediately

I have a Go program written that has a form which checks for the existence of a file by calling a fetch on a route inside the Go code. If the file exists or not, a boolean is return inside of a JSON as fileExists. I'm having trouble with the fetch call's JSON updating this.found boolean immediately.
What happens is that when I press enter or click the buttons, the form is submitted via call to onSubmit where the checkFile() is called which does the fetch. Somehow, I have to press enter twice to see the value returned by the fetch as it is not updating the this.found immediately. I am probably thinking about this the wrong way, but I figure it wouldn't to ask. Here's the code, if anyone can help so that clicking or submitting will be based on the correct value returned by the checkFile call:
<div class="jumbotron">
<div class="container">
<div class="alert alert-dark" role="alert">
</div>
<h1 class="display-3">Title</h1>
<div id="app">
<form ref="myForm" method="POST" v-on:submit.prevent="onSubmit" action="/push" class="needs-validation" id="myForm" novalidate="true">
<div class="form-group">
Canned List:
<input v-model="cannedlist" ref="cannedlist" type="text" class="form-control" name="fileListFile" id="filelist"
aria-describedby="fileListFileHelp"
autocomplete="off" :disabled="!!individuallist" v-on:submit.prevent="onSubmit" />
</div>
<div class="form-group">
Path List:
<textarea v-model="individuallist" ref="cannedlist" :disabled="!!cannedlist" class="form-control" rows=10 name="fileListRaw" id="files" autocomplete="off"></textarea>
</div>
<div class="form-group">
<button v-on:submit.prevent="onSubmit" type="submit" name="button" value="submit" id="submitButton" class="btn btn-primary" :disabled="isDisabled">Submit</button>
<button v-on:submit.prevent="onSubmit" type="submit" name="button" value="checkOnly" id="checkOnlyButton" class="btn btn-primary" :disabled="isDisabled">Submit 2</button>
</div>
</form>
</div>
</div>
</div>
<script src="/static/js/vue.min.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
// cannedlist: "filelist.txt",
individuallist: "",
found: false,
},
computed: {
isDisabled: function() {
//found = !found;
return (this.cannedlist.length <= 0 && this.individuallist.length <= 0);
},
},
methods: {
isDisabledNew: function() {
alert((this.cannedlist.length <= 0 && this.individuallist.length <= 0));
// return (this.cannedlist.length <= 0 && this.individuallist.length <= 0);
return false;
},
isFieldDisabled: function(e) {
//console.log(app.$refs.individuallist.disabled);
return false;
},
onSubmit: function() {
if (this.cannedlist.length > 0) {
this.checkFile();
if (this.found == true) {
this.$refs.myForm.submit();
return;
}
} else if (this.individuallist.length > 0) {
this.$refs.myForm.submit();
return;
}
},
checkFile: function() {
var url = 'http://localhost:9000/CheckIfFileExists?name=' + this.cannedlist;
return fetch(url)
.then(response => {
if (response.ok) {
var v = response.json().then( response => { this.found = response.fileExists; } );
return this.found;
}
return response.json().then(error => ({ error }));
});
return this.found;
},
}
});
</script>
Your onSubmit function calls checkFile and expects found to be updated:
onSubmit: function() {
if (this.cannedlist.length > 0) {
this.checkFile();
if (this.found == true) {
this.$refs.myForm.submit();
return;
}
} else if (this.individuallist.length > 0) {
this.$refs.myForm.submit();
return;
}
},
But checkFile returns a Promise. The Promise resolves by updating found. So you need to put your found checking inside a then block:
onSubmit: function() {
if (this.cannedlist.length > 0) {
this.checkFile()
.then(() => {
if (this.found == true) {
this.$refs.myForm.submit();
}
});
return;
} else if (this.individuallist.length > 0) {
this.$refs.myForm.submit();
return;
}
},
Here're the changes I made:
methods: {
onSubmit: function(event) {
if (this.cannedlist.length > 0) {
this.checkFile()
// This promise capture is the key I was missing
.then( (data) => {
this.found = data.fileExists;
if (this.found == true) {
this.$refs.myForm.submit();
} else {
alert("File not found: " + this.cannedlist);
}
});
} else if (this.individuallist.length > 0) {
this.$refs.myForm.submit();
}
},
checkFile: function() {
var url = 'http://localhost:9000/CheckIfFileExists?name=' + this.cannedlist;
return fetch(url).then((response) => response.json());
}