Slow DOM Change with v-show - vue.js

I've been troubleshooting an issue where, inside of a nested v-for loop, a simple v-show directive is taking up toward 4 seconds to actually apply the display: none; attribute in the DOM.
The basic layout is this. I've changed the actual naming to be all about cars; because why not!
I do apologize for the wall of code, but I felt it was relevant to include a basic jist of the component structure.
<div class="vehicle-history" v-if="fetchComplete && !fetchError">
<h3 class="header-bold">History</h3>
<div class="history-container" v-for="car in vehicles.cars" :key="car.id">
<div class="vehicle-header">
<div class="toyota" v-if="car.model == 'toyota'">
<span class="model-id">#{{ car.type }}_#{{ car.id }}</span> - #{{ car.title }}
</div>
<div class="audi" v-if="car.model == 'audi'">
<span class="model-id">#{{ car.type }}_#{{ car.id }}</span> - #{{ car.title }}
</div>
<div class="benz" v-if="car.model == 'benz'">
<span class="model-id">#{{ car.type }}_#{{ car.id }}</span> - #{{ car.title }}
</div>
<div class="rolls" v-if="car.model == 'rolls'">
<span class="model-id">#{{ car.type }}_#{{ car.id }}</span> - #{{ car.title }}
</div><br />
<small v-for="color in car.colors">#{{ color.name }} </small>
</div>
<div class="vehicle-body">
<div class="description">#{{ car.description }}</div>
<div class="review-container" v-if="car.reviews.length > 5">
<div class="review" v-for="n in (0, 4)" :key="car.reviews[n].id">
<strong>#{{ car.reviews[n].rating }}</strong> - #{{ car.reviews[n].content }}<br />
<small>#{{ car.reviews[n].created_at.date }} #{{ car.reviews[n].created_at.timezone }}</small>
<span v-show="n == 4 && !car.displayFull"><br />
<button type="button"
class="btn btn-link"
data-toggle="collapse"
v-on:click="car.displayFull = true"
v-bind:data-target="'#expanded-reviews-' + car.id">More details
</button>
</span>
</div>
<div class="collapse" v-bind:id="'expanded-reviews-' + car.id">
<div class="review" v-for="n in (4, car.reviews.length-1)" :key="car.reviews[n].id">
<strong>#{{ car.reviews[n].rating }}</strong> - #{{ car.reviews[n].content }}<br />
<small>#{{ car.reviews[n].created_at.date }} #{{ car.reviews[n].created_at.timezone }}</small>
<span v-if="n == car.reviews.length-1"><br />
<button type="button"
class="btn btn-link"
data-toggle="collapse"
v-on:click="car.displayFull = false"
v-bind:data-target="'#expanded-reviews-' + car.id">Less details
</button>
</span>
</div>
</div>
</div>
</div>
</div>
</div>
What I've tried
Adding the :key to the relevant loops
Moving the v-show attribute from the button (where it was originally at) to the parent span.
Wrapping it in <keep-alive> (which didn't work at all, it broke visibility as a whole)
Swapping v-show for v-if and vice-versa
The expected output
What I'm trying to accomplish is to make sure the button for "More details" hides away when the accordion gets expanded (that is, when the "More details" button is initially clicked) and re-appears if the "Less details" button is clicked.
The actual output
Whilst it does the above, there's a delay of between 2-5 seconds from clicking the button, to it getting the display: none; attribute applied in the DOM. The change to the Vue variable however appears instant.
Do you have any clue why this may be, and if there's a good workaround for this? Thank you, as always!
UPDATE
Revised code example below based on a number of suggestions. This is a flat out copy of existing code, so no changes to naming this time around.
<div class="incident-history text-center col-xs-12 padding-bottom" v-if="fetchComplete && !fetchError">
<h3 class="heading3 black-text">Incident History</h3>
<div class="incident-container" v-for="incident in responseData.incident.resolved" :key="incident.id">
<div class="incident-header">
<div :class="'title-' + incident.classification">
<span class="incident-id">#{{ incident.type }}_#{{ incident.id }}</span> - #{{ incident.title }}
</div><br />
<small v-for="monitor in incident.monitors">#{{ monitor.name }} </small>
</div>
<div class="incident-body">
<div class="description">#{{ incident.content }}</div>
<div class="update" v-for="(comment, index) in incident.comments.slice(0,5)">
<strong>#{{ comment.type }}</strong> - #{{ comment.content }}<br />
<small>#{{ comment.created_at.date }} #{{ comment.created_at.timezone }}</small>
</div>
<div class="collapse"
v-bind:id="'expanded-update-' + incident.id"
v-if="incident.comments.length > 4">
<div class="update" v-for="(comment, index) in incident.comments.slice(5)">
<strong>#{{ comment.type }}</strong> - #{{ comment.content }}<br />
<small>#{{ comment.created_at.date }} #{{ comment.created_at.timezone }}</small>
</div>
</div>
<button type="button"
class="btn btn-link"
data-toggle="collapse"
v-show="incident.comments.length > 4 && !incident.displayFull"
v-on:click="incident.displayFull = !incident.displayFull"
v-bind:data-target="'#expanded-update-' + incident.id">Show more
</button>
<button type="button"
class="btn btn-link"
data-toggle="collapse"
v-show="incident.comments.length > 4 && incident.displayFull"
v-on:click="incident.displayFull = !incident.displayFull"
v-bind:data-target="'#expanded-update-' + incident.id">Show less
</button>
</div>
</div>
</div>
I will note that the above code block is run inside of yet another for-loop. The basic topology is this (lengths may vary)
responseData: Object
incident: Object
active: Array[4]
0: Object
1: Object
Property: SomeValue
Property: SomeValue
Property: SomeValue
Property: SomeValue
Property: SomeValue
Comments: Array[3]
0: Object
1: Object
Property: SomeValue
Property: SomeValue
2: Object
2: Object
3: Object
Alternative Solution
I've come up with a working, and likely better alternative for the changing of "Read more" to "Read less" and vice versa.
button.full-details[aria-expanded="true"]:after {
content: 'Show less';
}
button.full-details[aria-expanded="false"]:after {
content: 'Show more';
}
This works fine, and just needs adding a few lines of CSS as well as a class to the button (or targeting the Bootstrap button class, but that's not great for many reasons).
That said, I'm going to leave this one open for a little bit to hopefully gain an understanding into why the issue arose with Vue in the first place.

It is surprising to hear 2-5 secs lag in display, but then there's quite a bit going on in that template. Hard to tell if suggested changes will impact the page visuals, without a test system.
The button(s) seems most problematic, creates one per review but only needs one per car, has to evaluate v-show or v-if for all of them.
Have one button outside the reviews loop,
<button class="btn btn-link" type="button"
data-toggle="collapse" :data-target="'#expanded-reviews-' + car.id"
#click="car.displayFull = !car.displayFull">
{{ buttonPrompt(car.displayFull) }}
</button>
I'd also consider creating a car child component, even a review grandchild component, as reasoning about the layout becomes difficult when it's that busy.
The final thing is so obvious, I'm thinking it must be an artifact of the domain renaming.
This
<div class="toyota" v-if="car.model == 'toyota'">
<span class="model-id">#{{ car.type }}_#{{ car.id }}</span> - #{{ car.title }}
</div>
<div class="audi" v-if="car.model == 'audi'">
<span class="model-id">#{{ car.type }}_#{{ car.id }}</span> - #{{ car.title }}
</div>
<div class="benz" v-if="car.model == 'benz'">
<span class="model-id">#{{ car.type }}_#{{ car.id }}</span> - #{{ car.title }}
</div>
<div class="rolls" v-if="car.model == 'rolls'">
<span class="model-id">#{{ car.type }}_#{{ car.id }}</span> - #{{ car.title }}
</div>
could become this
<div :class="car.model">
<span class="model-id">{{ car.type }}_{{ car.id }}</span> - {{ car.title }}
</div>
Also, what is this? #{{?
Edit after comment - on the question of how many buttons are created
It sounds like you are expecting only one or two buttons to be rendered? Here's a test.
From a spec for Cars.vue created with your template,
Test data has 6 reviews,
<script>
export default {
data () {
return {
fetchComplete: true,
fetchError: false,
cars: [
{
model: 'toyota',
type: 'hatch',
id: 1,
title: 'corolla',
description: 'grey corolla',
colors: [{name: 'grey'}, {name: 'grey'}, {name: 'grey'}],
reviews: [
{rating: 2, content: 'its grey', created_at: {date: ''}},
{rating: 2, content: 'its grey', created_at: {date: ''}},
{rating: 2, content: 'its grey', created_at: {date: ''}},
{rating: 2, content: 'its grey', created_at: {date: ''}},
{rating: 2, content: 'its grey', created_at: {date: ''}},
{rating: 2, content: 'its grey', created_at: {date: ''}},
]
}
]
}
},
The test,
it('count the buttons', () => {
const buttons = wrapper.element.querySelectorAll('button');
console.log(buttons)
})
Results,
LOG LOG: Object{
0: <button type="button" data-toggle="collapse" data-target="#expanded-reviews-1" class="btn btn-link">More details</button>,
1: <button type="button" data-toggle="collapse" data-target="#expanded-reviews-1" class="btn btn-link">More details</button>,
2: <button type="button" data-toggle="collapse" data-target="#expanded-reviews-1" class="btn btn-link">More details</button>,
3: <button type="button" data-toggle="collapse" data-target="#expanded-reviews-1" class="btn btn-link">More details</button>,
4: <button type="button" data-toggle="collapse" data-target="#expanded-reviews-1" class="btn btn-link">Less details</button>,
length: 5}
Then, there's the question of whether the change detection cycle is smart enough not to re-evaluate button creation each cycle. I'll see if we can test that too.
Placement of v-if
It just occurred to me, this
<button class="btn btn-link" ... >
<span v-if="n === car.reviews.length-1">Less details</span>
</button>
might be better as this
<button class="btn btn-link" ... v-if="n === car.reviews.length-1">
Less details
</button>
Missing first review
The v-for="n in (0, 4)" loop seems to miss out the first review. If I set the ratings to be sequential numbers, the first is not displayed.
<div class="review" v-for="n in (0, 4)">
<strong>#{{ car.reviews[n].rating }}</strong> - #{{ car.reviews[n].content }}
Try
<div class="review" v-for="(review,n) of car.reviews.slice(0,5)" :key="review.id">
<strong>#{{ review.rating }}</strong> - #{{ review.content }}
...
<div class="review" v-for="(review,n) of car.reviews.slice(5)">
(keeping n for button v-if expression).

Related

Vue's reactivity not triggered when using Bootstrap 5's alert div

Vue's reactivity not triggered when using Bootstrap 5's alert div.
See my code:
<template>
<div>
<div
v-if="alertICDMsg!==''"
id="alertICDCode"
class="alert alert-info alert-dismissible fade show"
role="alert"
>
<i class="fa-solid fa-lightbulb" />
<strong> Note!</strong> <span v-html="alertICDMsg" />
<button
type="button"
class="btn-close"
data-bs-dismiss="alert"
aria-label="Close"
/>
</div>
<div class="input-group mb-3">
<div class="col-xs-1">
<input
id="ICDCode"
v-model="editing_icdCode"
class="form-control"
placeholder="ICD Code"
aria-label="ICD Code"
#input="ICDCodeSearchRequested"
>
</div>
<input
id="Diagnosis"
v-model="editing_diagnosis"
type="text"
class="form-control"
placeholder="Diagnosis"
aria-label="Diagnosis"
aria-describedby="basic-addon1"
list="icdsearchlist"
#change="SelectedDiagnosisTextOption"
#input="ICDTextSearchRequested"
>
<datalist id="icdsearchlist">
<option
v-for="(disease_option, index) in icd_diagnosis_options"
:key="index"
>
{{ disease_option }}
</option>
</datalist>
<button
id="btnAddDiagnoses"
href="#"
class="btn btn-primary mx-1"
#click="AddDiagnosis"
>
<i class="fal fa-plus-circle" />
</button>
<button
id="btnCopyPreviousDiagnoses"
href="#"
class="btn btn-primary BtnSaveGroup mx-1"
>
<i class="far fa-history" />
</button>
<button
id="quickbill"
class="btn btn-primary mx-1"
>
<i class="fas fa-search-plus" />
</button>
<button
id="clearICD"
class="btn btn-danger mx-1"
#click="ClearICDFields"
>
<i class="fad fa-times-circle" />
</button>
</div>
</div>
<template>
<script>
export default {
data() {
return {
alertICDMsg:"",
};
},
watch: {
alertICDMsg: {
handler(val) {
console.log(`Val for alertICDMsg changed to :${val}`);
},
immediate: true,
deep: true,
},
},
methods: {
ICDCodeSearchRequested() {
console.log(`Search by ICD code`);
this.alertICDMsg="Searching in ICD Code box will search only by code. To search by a diagnosis name, type in the Diagnosis box."
console.log(`alertICDMsg is ${this.alertICDMsg}`);
setTimeout(function() {
console.log(`Dismissing alert`);
this.alertICDMsg='';
console.log(`alertICDMsg is ${this.alertICDMsg}`);
}, 5000);
},
},
}
</script>
Console log:
Search by ICD code
SubClinicalBlock.vue?d801:291 alertICDMsg is Searching in ICD Code box will search only by code. To search by a diagnosis name, type in the Diagnosis box.
SubClinicalBlock.vue?d801:220 Val for alertICDMsg changed to :Searching in ICD Code box will search only by code. To search by a diagnosis name, type in the Diagnosis box.
SubClinicalBlock.vue?d801:293 Dismissing alert
SubClinicalBlock.vue?d801:298 alertICDMsg is
The problem is that after 5 seconds, though the value of the variable changes, the alert is still visible.
I checked some similiar questions, and have seen this happening when bootstrap's javascript wasnt loaded. But for me, Bootstrap v5.0.1 JS is being loaded from the CDN and appears in the sources tab in Chrome.
Try to change the function inside of setTimeout to arrow function like this
setTimeout(() => { // code here })
The this inside of setTimeout(function () => {}) reference to the wrong context (the function itself) instead of the Vue component.
The arrow function doesn't have the this binding so when you use the arrow function the this keyword will reference the Vue component and change the state.
More info: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions

VUE's focus() method return a console error? How to use it correctly?

I'm trying to focus on several elements of my form but the first one, despite being applied, returns an error by console.
This is my template:
<div class="container">
<div class="col-xs-12">
<div class="row">
<h1 class="animal-title">Your selection is : </h1>
</div>
<div class="wrapper">
<form class="first-form" #submit.prevent="onSubmit">
<div class="image-wrapper">
<div class="sel-image">
<div v-on:click="imageSelected = true" v-for="item in items" v-bind:key="item.id">
<label>
<input
type="radio"
name="selectedItem"
ref="item"
:value="item.id"
v-model="itemFormInfo.selectedItem"
#change="onChangeItem($event)"
/>
<img v-if="item.id === 1" src="../../assets/1.png" />
<img v-if="item.id === 2" src="../../assets/2.png" />
<img v-if="item.id === 3" src="../../assets/3.png" />
</label>
<p class="cie-animal-subtitle">{{item.name}}</p>
</div>
</div>
</div>
<div class="form-select">
<div v-show="filteredStock && (imageSelected || itemFormInfo.selectedItem) > 0">
<h1 v-if="this.itemName === 'Phone' || this.itemName === 'Tablet'" for="selectedItem" ref="itemVisible">
Select the brand of your <span>{{this.itemName}}</span> :
</h1>
<h1 v-if="this.itemName === 'PC'" for="selectedBreed" ref="itemVisible">
Select the type of your <span>{{this.itemName}}</span> :
</h1>
<select
ref="brand"
class="form-control"
id="selectedBrand"
v-model="itemFormInfo.selectedBrand"
#change="onChangeBrand($event)">
<option v-for="brand in filteredBrand" v-bind:key="brand.name">{{ brand.name }}</option>
</select>
<div v-show="this.isBrandSelected">
<h1>What are you going to use your
<span>{{itemName}}</span> for ?
</h1>
<input
type="text"
id="componentName"
ref="componentName"
class="form-control fields"
style="text-transform: capitalize"
v-model="itemFormInfo.component"
#keypress="formChange($event)"
/>
<div class="loader-spinner" v-if="loading">
<app-loader/>
</div>
</div>
</div>
</div>
<div class="service-options" v-show="isComponentCompleted">
<div class="from-group">
<h1>
Here are the options for your <span>{{this.itemFormInfo.component}}</span> :
</h1>
<div class="services">
<div class="column-service" v-for="option in options" v-bind:key="option.name">
<div class="service-name">{{option.name}}</div>
<div class="service-price">{{option.price.toString().replace(".", ",")}} </div>
</div>
</div>
and here my first method
onChangeItem(event) {
let item = event.target._value;
this.itemName = this.getItemName(item);
if (this.isItemSelected = true) {
this.isItemSelected = false;
this.isComponentCompleted = false;
this.isLoaderFinished = false;
this.itemFormInfo.name = ""
}
this.$refs.item.focus();
},
in this function that I control my first input, the focus is working but it returns me by console the following error:
"this.$refs.item.focus is not a function at VueComponent.onChangeItem"
I have seen some references to similar cases where they involved the reference in a setTimeout or used the this.$nextTick(() => method but it didn't work in my case.
What am I doing wrong?
How can I focus on the next select with ref brand, once I have chosen the value of the first input?
Thank you all for your time and help in advance
How can I focus on the next select with ref brand, once I have chosen the value of the first input?
You want to put focus on brand but your onChangeItem handler is calling this.$refs.item.focus() (trying to focus item). Seems strange to me...
Reason for the error is you are using ref inside v-for.
Docs: When used on elements/components with v-for, the registered reference will be an Array containing DOM nodes or component instances
So the correct way for accessing item ref will be this.$refs.item[index].focus().
Just be aware that right now v-for refs do not guarantee the same order as your source Array - you can find some workarounds in the issue discussion...

How to chain selectors in order to get element inside with webdriverio

I have a page with list of products on it.
This is how HTML DOM looks like for one product item:
<div class="module card listing-search-card js-product-card " id="product-entry-123" data-product-id="123" data-toggle-status="open" data-out-of-stock="" data-toggle-isbundle="false" data-load-prices-async="false">
<div class="product-entry__wrapper">
<div class="card__header">
<div class="promotion">
<div class="product-entry__right promotion-card__body on-promotion--banner-offer">
</div>
<a href="/Products/p/123" tabindex="-1">
<picture>
<img class="card__image mobile-img lazyload" src="/medias/image-mobile">
<img class="card__image desktop-img lazyloaded" src="/medias/image-desktop">
</picture>
</a>
</div>
</div>
<div class="product-entry__body-actions-wrapper">
<div class="product-entry__body card__body">
<h3 class="card__title">
Schweppes
</h3>
<div class="product-entry__summary card__description-wrapper">
<div class="product-entry__summary__list">
<div class="card__detail-wrapper">
<div class="product-entry__summary__item card__description-product-detail">
33 x 24</div>
<div class="product-entry__summary__item card__description-product-code">
<span class="product-entry__code">
123</span>
</div>
</div>
<div class="container-type">
box</div>
</div>
</div>
</div>
<div class="cta-container">
<div class="card__amount-wrapper ">
<div class="card__amount">
61,83 € <span class="base-unit">HT/CHACUN</span>
<p class="sales-unit-price is-price">
<span>soit</span> 10,00 €
</span></span></p>
</div>
</div>
<div class="add-to-cart__footer add-to-cart__action">
<div class="success-overlay">Add to cart</div>
<div class="add-to-cart__action--active">
<div class="form-quantity__wrapper quantity-action quantity-action__wrapper"
data-form-quantity-id="123">
<div class="form-quantity ">
<button class="form-quantity__decrease quantity-action__decr icon-Minus disabled" type="button"
tabindex="-1" aria-label="decrement" data-form-quantity-decrement="">
</button>
<input id="product-123" class="form-quantity__input form-control quantity-action__value js-
quantity-input-typing" name="product-123" type="text" value="1" maxlength="4" data-price-
single="10.00" data-price-currency="€" data-parsley-range="[1,9999]" data-form-quantity-times="1"
data-parsley-multiplerange="1" data-parsley-type="integer" data-parsley-validation-threshold="1"
required="">
<button class="form-quantity__increase quantity-action__incr icon-Add-to-list" type="button"
tabindex="-1" aria-label="increment" data-form-quantity-increment="">
</button>
</div>
<span class="form-quantity__update" data-form-quantity-success=""></span>
</div>
<div class="add-to-cart__total">
<button class="button button--primary js-addToCart" role="button" title="Add
to cart" data-product-id-ref="123" data-modal-trigger="" data-modal-target="#add-to-cart-modal" data-
modal-before-trigger="addToCart" data-component-id="product list" tabindex="-1">
<div class="button__text">
<span class="button__text-add js-added-price">Add</span>
<span class="button__text-to-cart js-added-price">to cart</span>
</div>
<span class="button__text js-added-price mobile-only">Add</span>
</button>
</div>
</div>
</div>
<div class="add-to-template">
<button class="add-to-template--button button js-addToNewTemplate" type="button" data-modal-
trigger="" data-modal-target="#add-to-template-modal" data-modal-before-
trigger="openAddToTemplateModal" data-product-code="123">
<span>Add to list</span>
</button>
</div>
</div>
</div>
</div>
I am calling this function:
isSortedAlphabeticallyAscending($$('div.js-product-card'));
And the function implementation is:
isSortedAlphabeticallyAscending(list) {
for (let i = 0; i < (list.length - 1); i++) {
let outOfStockCurrent = list[i].getAttribute('data-out-of-stock');
let outOfStockNext = list[i + 1].getAttribute('data-out-of-stock');
let idCurrent = list[i].getAttribute('id');
let idNext = list[i + 1].getAttribute('id');
console.log("outOfStockCurrent " + outOfStockCurrent + " " + idCurrent);
console.log("outOfStockNext " + outOfStockNext + " " + idNext);
let productIdCurrent = idCurrent.split('-').pop();
let productIdNext = idNext.split('-').pop();
let currentText = list[i].$('a[href*="' + productIdCurrent + '"]').getText();
let nextText = list[i+1].$('a[href*="'+ productIdNext + '"]').getText();
console.log("currentText " + currentText);
console.log("nextText " + nextText);
if(outOfStockCurrent === "true" || outOfStockNext === "true") continue;
if (currentText > nextText) return false;
}
return true;
}
I ignore out of stock products since they are always at the bottom of the page.
But the list[i].$('a[href*="' + productIdCurrent + '"]').getText() is always returning empty text.
I would like it to get "Schweppes" text, i.e. product name.
Is there a way to chain somehow differently part with .$a[href ...] to get the text from the <a> tag inside the <div> element of the list of products using webdriverio 5?
Thanks!
The above selector list[i].$('a[href*="' + productIdCurrent + '"]').getText() targeted 2 elements.
What I needed to go one div further and find it there:
list[i].$('div.product-entry__body-actions-wrapper').$('a[href*="' + productIdCurrent + '"]').getText()
And voila, text appeared :)
Hope it will help someone with the similar issue :D

Cannot read property 'focus' of undefined in VUE When setting focus to button

I am new to vue I have component which if the endpoint fails, calls my generic 'Error' modal. All this is working fine but I keep getting the following error:
Cannot read property 'focus' of undefined
This only happens for the else part of my method function.
For this specific issue is I my 'failedPrcess' equals any of the following, this is when I get is, all others are fine:
existOrderSearchProdOrders
stockSearchStockLevels
cartFetchCouriers
Code
<template>
<div class="modal fade danger-modal" id="errorModal" tabindex="-1" role="dialog" aria-labelledby="errorModalTitle" aria-hidden="true"
data-keyboard="false" data-backdrop="static" style="z-index: 99999">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content danger-modal-content">
<div class="modal-header danger-modal-headerfooter">An error has occurred</div>
<div class="modal-body">
<p v-if="failedProcess === 'appGetAccount' || failedProcess === 'existOrderSearchProdOrders' || failedProcess === 'stockSearchStockLevels'
|| failedProcess === 'cartFetchCouriers'">
{{ contactTxt | capitalize }}
</p>
<p v-else-if="errorCount < 3">If the error continues, {{ contactTxt }}</p>
<p v-else>As the error has continued, {{ contactTxt }}</p>
<p>
<b>
01234 567890
<br />
Open from 00:00 to 07:00
</b>
</p>
<p>Advising of what you were doing when the error occurred.</p>
</div>
<div class="modal-footer danger-modal-headerfooter">
<a v-if="failedProcess === 'appGetAccount'" ref="logoutButton" class="btn btn-primary" :class="logoutButtClicked" #click="logoutClicked = true" href="/site/logout">
<span v-if="!logoutClicked" id="logoutButtonLabel">Logout</span>
<span v-else id="logoutSpinner">
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
Logging out
</span>
</a>
<router-link v-else-if="failedProcess === 'fetchOrderReportDetails'" to="/review" tag="button"
ref="existOrdersButton" class="btn btn-primary" type="button" data-dismiss="modal" #click.native="closeButton">
Return to existing orders
</router-link>
<button v-else-if="errorCount < 3 && (failedProcess !== 'productsFetchProducts' && failedProcess !== 'existOrderSearchProdOrders'
&& failedProcess !== 'stockSearchStockLevels' && failedProcess !== 'cartFetchCouriers')" ref="closeButton" class="btn btn-primary"
type="button" data-dismiss="modal" #click="closeButton">
Close
</button>
<router-link v-else to="/" tag="button" ref="homeButton" class="btn btn-primary" type="button" data-dismiss="modal" #click="closeButton">
Return to homepage
</router-link>
</div>
</div>
</div>
</div>
</template>
<script>
import * as params from '../params';
export default {
name: "ErrorModal",
data() {
return {
contactTxt: 'please contact us on:',
errorCount: 0,
failedProcess: '',
}
},
mounted() {
VueEvent.$on('show-error-modal', (failedProcess) => {
if (this.failedProcess !== failedProcess) {
this.errorCount = 0;
}
this.failedProcess = failedProcess;
$('#errorModal').modal('show').on('shown.bs.modal', this.focus);
});
},
methods: {
focus() {
if (this.failedProcess === 'appGetAccount') {
this.$refs.logoutButton.focus();
} else if (this.failedProcess === 'fetchOrderReportDetails') {
this.$refs.existOrdersButton.$el.focus();
} else if (this.errorCount < 3 && this.failedProcess !== 'productsFetchProducts') {
this.$refs.closeButton.focus();
} else {
this.$refs.homeButton.$el.focus();
}
},
}
}
</script>`enter code here`
I've tried using v-if before and I also had similar problems and the best solution I found was, instead of using v-if/v-else-if/v-else, use v-show instead to perform conditional rendering.
Also, as the Vue.js doc says:
Generally speaking, v-if has higher toggle costs while v-show has higher initial render costs. So prefer v-show if you need to toggle something very often, and prefer v-if if the condition is unlikely to change at runtime.

With vee-validate make custom validation

In my vue 2.5.17 app I have a select input and text input and I have a create a validation rule that only 1 of them musdt be filled :
<div class="form-row p-3">
<label for="existing_user_list_id" class="col-12 col-sm-4 col-form-label">Select already existing list</label>
<div class="col-12 col-sm-8">
<v-select
v-model="selection_existing_user_list_id"
data-vv-name="selection_existing_user_list_id"
:options="userListsByUserIdArray"
v-validate="''"
id="existing_user_list_id"
name="existing_user_list_id"
class="form-control editable_field"
placeholder="Select existing user list"
v-on:input="onChangeSelectionExistingUserListId($event);"
></v-select>
<span v-show="vueErrorsList.has('existing_user_list_id')" class="text-danger">{{ vueErrorsList.first('existing_user_list_id') }}</span>
</div>
</div>
<div class="form-row p-1 m-1 ml-4">
<strong>OR</strong>
</div>
<div class="form-row p-2`">
<label for="new_user_list_title" class="col-12 col-sm-4 col-form-label">Create a new list</label>
<div class="col-12 col-sm-8">
<input class="form-control" value="" id="new_user_list_title" v-model="new_user_list_title" #change="onChangeBewUserListTitle($event);">
<span v-show="vueErrorsList.has('title')" class="text-danger">{{ vueErrorsList.first('title') }}</span>
</div>
</div>
I found this :
https://baianat.github.io/vee-validate/guide/custom-rules.html#creating-a-custom-rule
But I am confused how to use it in my case. I try :
import { Validator } from 'vee-validate';
Validator.extend('new_user_list_title', {
getMessage: field => 'The ' + field + ' value is not a valid new_user_list_title.',
validate: value => false // for simplicity I try to raise error always
// validate: value => value.length == 0 && selection_existing_user_list_id == null
})
But my custom error is never triggered, even when I set always false,
Which is right way ?
Thanks!
After adding custom rule, you need to use it in component (in v-validate):
<input
class="form-control"
id="new_user_list_title"
v-model="new_user_list_title"
#change="onChangeBewUserListTitle($event);"
v-validate="'new_user_list_title'">