Conditionally remove element from DOM depending on screen size - vue.js

In my Vue/Vuetify app, I can conditionally hide elements depending on the screen size using the Vuetify display helper classes, e.g.
<!-- Show on medium width and above -->
<v-app-bar app height="74px" class="hidden-sm-and-down">
<button #click="logout" data-cy="logout">Log Out</button
</v-app-bar>
<!-- Show on small width and below -->
<v-app-bar app height="74px" class="hidden-md-and-up">
<button #click="logout" data-cy="logout">Log Out</button
</v-app-bar>
The element is hidden by setting the CSS property display: none. This causes the following Cypress command to fail
cy.get('[data-cy="logout"]').click()
With the error
cy.click() can only be called on a single element. Your subject contained 2 elements
So evidently Cypress doesn't ignore elemnts with display: none.
Is there a way I either remove these elements instead of hiding them, or alternatively tell Cypress to ignore hidden elements?

Add the data property "removeElement" to your data section.
Add a watcher for the Vuetify breakpoint attribute, and set the "removeElement" to true/false depending on when you need to remove/add the element.
watch: {
'$vuetify.breakpoint.width'(val) {
if (val < 425)
this.removeElement = true
else
this.removeElement = false
},
},
Update your template to use v-if="removeElement" instead of class=" .... "

The jquery :visible selector works, when the parent/ancestor has display: none.
Ref visibility
An element is considered hidden if:
...
Its CSS property (or ancestors) is display: none.
Test fiddle
/// <reference types="#cypress/fiddle" />
const test = {
html: `
<div>
<header style="display: none">
<button data-cy="logout">Button small</button>
</header>
<header>
<button data-cy="logout">Button medium</button>
</header>
</div>
`,
test: `
cy.get('[data-cy="logout"]:visible').click()
`
}
it('the test', () => {
cy.runExample(test)
})

Related

Change the CSS if dynamically changing image is loaded in Vue 3

I'm trying to change the CSS class of a div based on if an image is loaded. The image URL is taken from an <input> field, so <img src> can change. I've managed to set the CSS class on the first page-load using the #load event. But if I change the image's URL in the input field to a non-existent image, then the CSS doesn't change. How do I track if the input's value has changed and "re-check" if the image is loaded?
In the below example, I want to have green-bg if the image exists and red-bg if the image doesn't exist.
<div id="app">
<div :class="imgLoaded ? 'green-bg' : 'red-bg'">
<img :src=imgURL #load="imgLoaded = true" />
<br/>
<input v-model="imgURL" />
</div>
</div>
<script>
export default {
data() {
return {
imgURL: 'https://picsum.photos/200/300',
imgLoaded: false,
};
}
};
</script>
Link to CodePen
This is a nice place to use a watcher.
watch: {
imgURL(newVal, oldVal) {
if (newVal !== oldVal) this.imgLoaded = false
}
}
Hooking to the input event would work too, but there is an edge case where the new value is similar to the old value. If you then set the imgLoaded to false, the CSS class won't change to green-bg because the load event does not fire (since the url did not change).
When using a watcher you can compare the old value to the new value and only set imgLoaded to false if the values are different.
Here is the pen.
Add to you input:
<input type="text" v-model="imgURL" #input="updateURL">
and create function on methodth section:
updateURL() {
this.imgLoaded = false
}
I managed to solve my issue by using the #error method. imgLoaded would be set to false on error and set to true on load.
<div id="app">
<div :class="imgLoaded ? 'green-bg' : 'red-bg'">
<img :src=imgURL #load="imgLoaded = true" #error="imgLoaded = false" />
<br/>
<input v-model="imgURL" />
</div>
</div>
This works for the case where the CSS class is changed based on whether the image exists or not.

Vuejs Unknown custom element (Imported component error)

I basically import 2-3 components in a parent component and based on the button clicks I simply hide and show some components. In every component I have Back and Next buttons, when they clicked, I show different components in different situations. Here's a simple example of one my components;
<template>
<div>
<transition name="fade" appear>
<div class="justify-center">
<form #submit.prevent="submitFormTest" v-if="!back && !configuration">
Bunch of code here is irrelevant with my question...
<!-- Buttons -->
<div class="text-center mt-4">
<button #click="back = true" class="btn-w-orange mr-2" type="button">Back</button>
<button #click="nextButton" class="btn-w-orange" type="button">Next</button>
</div>
</form>
<modalOpMode v-if="back"></modalOpMode>
<modalConnection v-if="configuration && !back"></modalConnection >
</div>
</transition>
</div>
</template>
and here is my script;
<script>
import modalConnection from "./modalConnection";
import modalOpMode from "./modalOpMode "
export default {
data(){
return {
configuration: false,
back: false
}
},
components: {
modalOpMode,
modalConnection,
},
methods: {
nextButton(){
this.configuration = true;
},
},
}
</script>
I statically imported two other components to display when back and next button is clicked. Everything is okay when I go for Next button for ALL MY COMPONENTS. (There are other components that I get the same error.). But the problem occurs when I click the back button. When Next is clicked, the form is hidden and modalConnection is visible as it should but when Back is clicked, I only see a blank page because the modalOpMode gives me the following error;
[Vue warn]: Unknown custom element: <modalOpMode> - did you register the component correctly? For recursive components, make sure to provide the "name" option.
I'm pretty sure that I import the component in the correct way, the path, the component name, the usage, everything is normal because I do that with the other one modalConnection and that one works all good. If you want to check my modalOpMode component, here it is;
<template>
<transition name="fade" appear>
<div>
<form #submit.prevent="submitFormTest" v-if="configuration == ''">
There are four radio buttons that bound with v-model to change the value of configuration variable
</form>
<!-- Modal Comp1-->
<modalComp1 v-if="configuration == 'comp1'"></modalComp1>
<!-- Modal Comp2 -->
<modalComp2 v-if="configuration == 'comp2'"></modalComp2>
<!-- Modal Comp3-->
<modalComp3 v-if="configuration == 'comp3'"></modalComp3>
<!-- Modal Comp4 -->
<modalComp4 v-if="configuration == 'comp4'"></modalComp4 >
</div>
</transition>
</template>
In here I imported 4 other components and bound them to a variable called configuration. When the variable is equal to something like comp1 or comp2 as above, I simply display the relevant component and hide the other content.
As I said before, when I click the next button and show the components, it's all good, but I can't go back, it gives me the above error. What am I doing wrong or what am I missing here? Thanks in advance
In Components you have modalOperatingMode instead of modalOpMode

Vue Bootstrap, how to interact with plus/minus icon on dynamic generated collapse content separately

I have a VueJS view that creates collapsed contents using Bootstrap Vue Collapse Component.
The data is dynamic and can contains hundreds of items, which is why you see in the code below it was created via a v-for loop in Vue.
<div class="inventory-detail" v-for="(partNumberGroup,index) in inventory" :key="index" >
<b-button block v-b-toggle="partNumberGroup.partNumber" v-bind:id="partNumberGroup.partNumber" variant="primary"
#click="(evt) =>{isActive = !isActive && evt.target.id == partNumberGroup.partNumber}">
<i v-bind:id="partNumberGroup.partNumber" class="float-right fa" :class="{ 'fa-plus': !isActive, 'fa-minus': isActive }"></i>
{{ partNumberGroup.partNumber }}
</b-button>
<div class="inventory-detail__card" v-for="item in partNumberGroup.items">
<b-collapse v-bind:id="partNumberGroup.partNumber" >
<b-card>
<!--Accordion/Collapse content -->
</b-card>
</b-collapse>
</div>
</div>
This works fairly well in that I can individually expand and collapse each content separately. However, the one issue I'm facing is each time I click the icon fa-minus (-) orfa-plus (+), all of them changed as per the images below.
Any tips on how I should implementing this? in my code I tried the dynamic CSS class switching but I still lack the ability to switch on specific element.
I feel like the solution to this is to somehow conditionally apply dynamic CSS class or somehow able to use the attribute 'aria-expanded'.
You can try something like this. Whenever somebody clicks on the icon, set its index as activeIndex (using the setActiveIndex method). Then you can set the class accordingly by comparing the activeIndex with current index
<i
#click="setActiveIndex(index)"
v-bind:id="partNumberGroup.partNumber"
class="float-right fa"
:class="{ 'fa-plus': !isActive(index), 'fa-minus': isActive(index) }">.
</i>
then in the script part:
...
data() {
return {
activeIndex: -1
}
},
methods: {
/* set active index on click */
setActiveIndex(index) {
this.activeIndex = index;
},
/* check if index is active or not */
isActive(index) {
return index === this.activeIndex;
}
}

Conditional wrapper rendering in vue

I'm making a link/button component which either can have a button or an anchor wrapper, a text and an optional icon. My template code below is currently rendering either an anchor or a button (with the exact same content) based on an if statement on the wrapper element, resulting in duplicate code.
<template>
<a v-if="link" v-bind:href="url" class="btn" :class="modifier" :id="id" role="button" :disabled="disabled">
{{buttonText}}
<svg class="icon" v-if="icon" :class="iconModifier">
<use v-bind="{ 'xlink:href':'#sprite-' + icon }"></use>
</svg>
</a>
<button v-else type="button" class="btn" :class="modifier" :id="id" :disabled="disabled">
{{buttonText}}
<svg class="icon" v-if="icon" :class="iconModifier">
<use v-bind="{ 'xlink:href':'#sprite-' + icon }"></use>
</svg>
</button>
</template>
Is there a more clean way for wrapping my buttonText and icon inside either an anchor or button?
I've solved my issue by intensive Google-ing! Found this issue regarding Vue on Github which pointed me in the right direction.
Small piece of backstory
I'm using Vue in combination with Storybook to build a component library in which a button can either be a button or an anchor. All buttons look alike (apart from color) and can be used for submitting or linking. To keep my folder structure ordered, I would like a solution that generates a multiple buttons types (with or without link) from one single file.
Solution
Using computed properties I'm able to "calculate" the necessary tag, based on the url property of my component. When a url is passed, I know that my button has to link to another page. If there is no url property it should submit something or preform a custom click handler (not in the sample code below).
I've created the returnComponentTag computed property to avoid placing any complex or bulky logic (like my original solution) in my template. This returns either an a or a button tag based on the existence of the url property.
Next, as suggested by ajobi, using the :is attribute I'm able to define the component tag based on the result of my computed property. Below a stripped sample of my final (and working) solution:
<template>
<component :is="returnComponentTag" v-bind:href="url ? url : ''" class="btn" :class="modifier" :id="id">
{{buttonText}}
</component>
</template>
<script>
export default {
name: "Button",
props: {
id: {
type: Number
},
buttonText: {
type: String,
required: true,
default: "Button"
},
modifier: {
type: String,
default: "btn-cta-01"
},
url: {
type: String,
default: ""
}
},
computed: {
returnComponentTag() {
return this.url ? "a" : "button"
}
}
};
</script>
You could extract the wrapping element into a dedicated component.
<template>
<a v-if="link" v-bind:href="url" class="btn" :class="modifier" :id="id" role="button" :disabled="disabled">
<slot></slot>
</a>
<button v-else type="button" class="btn" :class="modifier" :id="id" :disabled="disabled">
<slot></slot>
</button>
</template>
// You would use it like this
<SomeComponent /* your props here */ >
{{buttonText}}
<svg class="icon" v-if="icon" :class="iconModifier">
<use v-bind="{ 'xlink:href':'#sprite-' + icon }"></use>
</svg>
</SomeComponent>
There are multiple ways of doing this. Two examples would be the following based on the point of view:
You are defining two different components (Button or Anchor) and want to use a wrapper to render either one of them.
You could seperate the Wrapper Content into two components so that the wrapper only decides on which of the components to render (either the Button or the Anchor).
The problem with this approach could be you will have doubled code for methods and styling for the button and anchor component.
You are defining the content as a component and use the wrapper to define what to wrap the content in.
See Answer of https://stackoverflow.com/a/60052780/11930769
It would be great to know, why you would want to achive this. Maybe there are better solutions for your usecase. Cheers!

iview ui how to clear files after successfully uploaded?

I am trying to clear the file lists as iview ui by default show file list. The problem is, user can upload file different times and ivew ui upload component keeps the old files in the list. I don't from where the list is coming. I saw there is a method clearFiles but not sure how to use it. There is no example in doc.
This is how I am using.
One thing, if I make :show-upload-list to false the list doesn't show but the progress bar also doesn't show. I want the progress bar to stay and list shouldn't show up.
<Upload
:multiple="false"
:show-upload-list="true"
:on-success="handleSuccess"
:format="['jpg','jpeg','png', 'pdf', 'docx', 'txt', 'mp4', 'mp3', 'zip']"
:max-size="21048"
:on-format-error="handleFormatError"
:on-exceeded-size="handleMaxSize"
type="drag"
:action="isFileUpload.url"
:data="isFileUpload.meta"
>
<div style="padding: 20px 0">
<Icon type="ios-cloud-upload" size="52" style="color: #3399ff"></Icon>
<p>Click or drag files here to upload</p>
</div>
Thank you.
You can add ref="upload" to vue component and clear file with this.$refs.upload.clearFiles()
<template>
<Upload
ref="upload"
:multiple="false"
:show-upload-list="true"
:on-success="handleSuccess"
:format="['jpg','jpeg','png', 'pdf', 'docx', 'txt', 'mp4', 'mp3', 'zip']"
:max-size="21048"
:on-format-error="handleFormatError"
:on-exceeded-size="handleMaxSize"
type="drag"
:action="isFileUpload.url"
:data="isFileUpload.meta"
>
<div style="padding: 20px 0">
<Icon type="ios-cloud-upload" size="52" style="color: #3399ff"></Icon>
<p>Click or drag files here to upload</p>
</div>
</template>
<script>
export default {
...
methods: {
handleSuccess () {
this.$refs.upload.clearFiles()
}
}
...
}
</script>