How can I add vuetify dialog into existing application? - vue.js

I created a vue dialog app/component using vue cli. It consist of a sample button to be clicked on to imitate how the dialog (What I need) will be loaded when a link on the existing application is clicked. I have a couple of issues.
When using v-app it adds the application wrapper I dont need seeing as its only the dialog I want. It creates a huge whitespace not needed. If I remove it, it errors [Vuetify] Unable to locate target [data-app] and the dialog wont load when <div #click='getInformation('USA')'></div> in the existing application is used.
Tried removing v-app and just using template but continues to error. Seems I need to still specify v-app in some way. Lost here
An example on how Im trying to pull it off but not working in App.vue
<template>
<div v-resize="onResize">
<v-dialog>
<v-card>
{{ information }}
</v-card>
</v-dialog>
</div>
</template>
<script>
export default {
data() {
return {
isMobile: false,
information: []
};
},
methods: {
onResize() {
if (window.innerWidth < 425) this.isMobile = true;
else this.isMobile = false;
},
getInformatiom(country) {
axios
.get(`${api}/${country}/info`, {
headers: {
Authorization: `token`
}
})
.then(response => {
this.information = response.data.info;
});
}
}
};
main.js
import Vue from "vue";
import App from "./App.vue";
import Vuetify from "vuetify";
import "vuetify/dist/vuetify.min.css";
Vue.use(Vuetify);
Vue.config.productionTip = false;
new Vue({
render: h => h(App)
}).$mount("#app");
Dialog component is ready to go, just having so much trouble getting it to show when its being called from the existing application. Just a note, the existing application does not use Vue, its only classic asp, Im only updating the dialog on the page to look/work better using vue/vuetify. Any help would be GREATLY APPRECIATED

You NEED the v-app element with vuetify.
Try this to only use the app when showing the dialog. Then use CSS to customise the v-app.
<v-app v-if='this.information && this.information.length'>
<v-dialog>...</v-dialog>
</v-app>

I would use the max-width prop of v-dialog, make it dynamic by adding :max-width and then have that bound to a computed property which subscribes to your screen size. I would not try to control it from an external div. See here for full list of sizing options
https://vuetifyjs.com/en/components/dialogs

Related

How can I wrap a vue component during cypress component testing?

I am using component testing in Cypress on Vue. My project components use the vuetify plugin.
Currently, tested components load with Vuetify:
import DebuggingTemporaryComponent from "./DebuggingTemporaryComponent";
import {mount} from "#cypress/vue";
import vuetify from '../../resources/js/vuetify'
it('mounts the component with vuetify', () => {
mount(DebuggingTemporaryComponent,{vuetify,})
cy.contains('Hello World') ✅
}
However, the stylings are not functioning correctly because Vuetify components need to be wrapped in a <v-app> at least once on the page. In component testing this is not happening.
I need to customise the wrapper as suggested here in the docs for the React equivalent. However whenever I try to make my own function to do this, I get an error that the appropriate webpack loader isn't there. Vue loader is there and working.
import {mount as cypressMount} from '#cypress/vue'
export function mount (component){
return cypressMount(<v-app>component</v-app>, prepareComponent(props, options))
}
Can anyone help me with where to go next with this?
You can construct a simple wrapper in the test, for example
Component to test - Button.vue
<template>
<v-btn color="red lighten-2" dark>
Click Me
</v-btn>
</template>
Test
import Button from "./Button";
import {mount} from "#cypress/vue";
import vuetify from '../plugins/vuetify'
import { VApp } from 'vuetify/lib/components'
const WrapperComp = {
template: `
<v-app>
<Button />
</v-app>
`,
components: {
VApp,
Button
}
}
it('mounts the component with vuetify', () => {
mount(WrapperComp, { vuetify })
const lightRed = 'rgb(229, 115, 115)'
cy.contains('button', 'Click Me') // ✅
.should('have.css', 'background-color', lightRed) // fails if no <v-app> used above
})
You get an error because you are trying to use JSX which is indeed possible with Vue but you need to configure additional build plugins.
Same can be achieved without JSX by using render function
import {mount} from "#cypress/vue";
import vuetify from '../../resources/js/vuetify'
import { VApp } from 'vuetify/lib/components'
function mountComponentWithVuetify(componentToMount, options = {})
{
return mount({
render(h) {
return h(VApp, [h(componentToMount)])
}
},
{
vuetify,
...options,
})
}

Why is my `client-only` component in nuxt complaining that `window is not defined`?

I have Vue SPA that I'm trying to migrate to nuxt. I am using vue2leaflet in a component that I enclosed in <client-only> tags but still getting an error from nuxt saying that window is not defined.
I know I could use nuxt-leaflet or create a plugin but that increases the vendor bundle dramatically and I don't want that. I want to import the leaflet plugin only for the components that need it. Any way to do this?
<client-only>
<map></map>
</client-only>
And the map component:
<template>
<div id="map-container">
<l-map
style="height: 80%; width: 100%"
:zoom="zoom"
:center="center"
#update:zoom="zoomUpdated"
#update:center="centerUpdated"
#update:bounds="boundsUpdated"
>
<l-tile-layer :url="url"></l-tile-layer>
</l-map>
</div>
</template>
<script>
import {
LMap,
LTileLayer,
LMarker,
LFeatureGroup,
LGeoJson,
LPolyline,
LPolygon,
LControlScale
} from 'vue2-leaflet';
import { Icon } from 'leaflet';
import 'leaflet/dist/leaflet.css';
// this part resolve an issue where the markers would not appear
delete Icon.Default.prototype._getIconUrl;
export default {
name: 'map',
components: {
LMap,
LTileLayer,
LMarker,
LFeatureGroup,
LGeoJson,
LPolyline,
LPolygon,
LControlScale
},
//...
I found a way that works though I'm not sure how. In the parent component, you move the import statement inside component declarations.
<template>
<client-only>
<map/>
</client-only>
</template>
<script>
export default {
name: 'parent-component',
components: {
Map: () => if(process.client){return import('../components/Map.vue')},
},
}
</script>
<template>
<client-only>
<map/>
</client-only>
</template>
<script>
export default {
name: 'parent-component',
components: {
Map: () =>
if (process.client) {
return import ('../components/Map.vue')
},
},
}
</script>
The solutions above did not work for me.
Why? This took me a while to find out so I hope it helps someone else.
The "problem" is that Nuxt automatically includes Components from the "components" folder so you don't have to include them manually. This means that even if you load it dynamically only on process.client it will still load it server side due to this automatism.
I have found the following two solutions:
Rename the "components" folder to something else to stop the automatic import and then use the solution above (process.client).
(and better option IMO) there is yet another feature to lazy load the automatically loaded components. To do this prefix the component name with "lazy-". This, in combination with will prevent the component from being rendered server-side.
In the end your setup should look like this
Files:
./components/map.vue
./pages/index.html
index.html:
<template>
<client-only>
<lazy-map/>
</client-only>
</template>
<script>
export default {
}
</script>
The <client-only> component doesn’t do what you think it does. Yes, it skips rendering your component on the server side, but it still gets executed!
https://deltener.com/blog/common-problems-with-the-nuxt-client-only-component/
Answers here are more focused towards import the Map.vue component while the best approach is probably to properly load the leaflet package initially inside of Map.vue.
Here, the best solution would be to load the components like so in Map.vue
<template>
<div id="map-container">
<l-map style="height: 80%; width: 100%">
<l-tile-layer :url="url"></l-tile-layer>
</l-map>
</div>
</template>
<script>
import 'leaflet/dist/leaflet.css'
export default {
name: 'Map',
components: {
[process.client && 'LMap']: () => import('vue2-leaflet').LMap,
[process.client && 'LTileLayer']: () => import('vue2-leaflet').LTileLayer,
},
}
</script>
I'm not a leaflet expert, hence I'm not sure if Leaflet care if you import it like import('vue2-leaflet').LMap but looking at this issue, it looks like it doesn't change a lot performance-wise.
Using Nuxt plugins is NOT a good idea as explained by OP because it will increase the whole bundle size upfront. Meaning that it will increase the loading time of your whole application while the Map is being used only in one place.
My How to fix navigator / window / document is undefined in Nuxt answer goes a bit more in detail about this topic and alternative approaches to solve this kind of issues.
Especially if you want to import a single library like vue2-editor, jsplumb or alike.
Here is how I do it with Nuxt in Universal mode:
this will: 1. Work with SSR
2. Throw no errors related to missing marker-images/shadow
3. Make sure leaflet is loaded only where it's needed (meaning no plugin is needed)
4. Allow for custom icon settings etc
5. Allow for some plugins (they were a pain, for some reason I thought you could just add them as plugins.. turns out adding them to plugins would defeat the local import of leaflet and force it to be bundled with vendors.js)
Wrap your template in <client-only></client-only>
<script>
let LMap, LTileLayer, LMarker, LPopup, LIcon, LControlAttribution, LControlZoom, Vue2LeafletMarkerCluster, Icon
if (process.client) {
require("leaflet");
({
LMap,
LTileLayer,
LMarker,
LPopup,
LIcon,
LControlAttribution,
LControlZoom,
} = require("vue2-leaflet/dist/vue2-leaflet.min"));
({
Icon
} = require("leaflet"));
Vue2LeafletMarkerCluster = require('vue2-leaflet-markercluster')
}
import "leaflet/dist/leaflet.css";
export default {
components: {
"l-map": LMap,
"l-tile-layer": LTileLayer,
"l-marker": LMarker,
"l-popup": LPopup,
"l-icon": LIcon,
"l-control-attribution": LControlAttribution,
"l-control-zoom": LControlZoom,
"v-marker-cluster": Vue2LeafletMarkerCluster,
},
mounted() {
if (!process.server) //probably not needed but whatever
{
// This makes sure the common error that the images are not found is solved, and also adds the settings to it.
delete Icon.Default.prototype._getIconUrl;
Icon.Default.mergeOptions({
// iconRetinaUrl: require('leaflet/dist/images/marker-icon-2x.png'), // if you want the defaults
// iconUrl: require('leaflet/dist/images/marker-icon.png'), if you want the defaults
// shadowUrl: require('leaflet/dist/images/marker-shadow.png') if you want the defaults
shadowUrl: "/icon_shadow_7.png",
iconUrl: "/housemarkerblue1.png",
shadowAnchor: [10, 45],
iconAnchor: [16, 37],
popupAnchor: [-5, -35],
iconSize: [23, 33],
// staticAnchor: [30,30],
});
}
},
And there's proof using nuxt build --modern=server --analyze
https://i.stack.imgur.com/kc6q4.png
I am replicating my answer here since this is the first post that gets reached searching for this kind of problem, and using the solutions above still caused nuxt to crash or error in my case.
You can import your plugin in your mounted hook, which should run in the client only. So:
async mounted() {
const MyPlugin = await import('some-vue-plugin');
Vue.use(MyPlugin);
}
I do not know about the specific plugin you are trying to use, but in my case I had to call Vue.use() on the default property of the plugin, resulting in Vue.use(MyPlugin.default).

Trying to use ref inside a Vue single file component. Got undefined

I'm beginning with vuejs and I try to figure what could be done about reference of child component instance in root instance. I used ref attribute and it works pretty well, except if I use it in a single file component (in the template tags). In this specific case, I get 'undefined'.
So, I try to understand why, because it could be very useful for establishing dynamic references. I could probably bypass that situation easily, but I would like to understand the problem instead of run away.
So if someone have an idea ;)
I am using webpack to import my single file component in my app.js and compiled it. However the template compilation isn't done by webpack, but by the browser at runtime (maybe it's the beginning of an explanation ?).
My app is very simple, and I log my references on click on the header, so I don't think it's lifecylce callback related.
Here is my files :
app.js
import Vue from 'Vue';
import appButton from './appButton.vue';
import appSection from './appSection.vue';
var app = new Vue({
el: '#app',
components:
{
'app-button' : appButton
},
methods:
{
displayRefs: function()
{
console.log(this.$refs.ref1);
console.log(this.$refs.ref2);
console.log(this.$refs.ref3);
}
}
});
my component appButton.vue
<template>
<div ref="ref3" v-bind:id="'button-'+name" class="button">{{label}}</div>
</template>
<script>
module.exports =
{
props: ['name', 'label']
}
</script>
my index.html body
<body>
<div id="app">
<div id="background"></div>
<div id="foreground">
<img id="photo" src="./background.jpg"></img>
<header ref="ref1">
<h1 v-on:click="displayRefs">My header exemple</h1>
</header>
<nav>
<app-button ref="ref2" name="presentation" label="Qui sommes-nous ?"></app-button>
</nav>
</div>
</div>
<script src="./app.js"></script>
</body>
ref1 (header tag) and ref2 (app-button tag) are both found. But ref3 (in my single file component) is undefined. Also
Thanks for all the piece of answer you could give me, hoping it's not a silly mistake.
A ref you set is only accessible in the component itself.
If you try to console.log(this.$refs.ref3); into a method from appButton.vue, it will work. But it won't work in the parent.
If you want to access that ref from the parent, you need to use the $ref2 to access the component, and then use the $ref3. Try this:
var app = new Vue({
el: '#app',
components:
{
'app-button' : appButton
},
methods:
{
displayRefs: function()
{
console.log(this.$refs.ref1);
console.log(this.$refs.ref2);
console.log(this.$refs.ref2.$refs.ref3); // Here, ref3 will be defined.
}
}
});
Accessing some child ref from the parent is not supposed to be a good practice tho.

Nested single file components - vue.js with electron-forge

I am trying electron for the first time and I am blown away by it. I have hit a wall, though, when trying to use single file vue.js components using electron-forge. My problem is the following:
I create a project using the vue.js template and run it. Works and looks great. I have a single file page with an index file that looks like this:
<div id="test"></div>
</body>
<script>
import Vue from 'vue';
import Test from './test';
const app = new Vue(Test).$mount('#test');
app.text = "Electron Forge with Vue.js!";
</script>
So far, so good. It imports Test, which is a single file component and renders it.
Now, I would like to have other single file components nested in this main component. For example, I would like to have the following, in my app file called test.vue
<template>
<h2>Hello from {{text}}</h2>
</template>
<script>
import About from './About.vue'
export default {
components: {
appAbout: About,
},
data () {
return {
text: 'Electron'
}
}
}
</script>
Again, so far so good. I can run the app with no errors so the component is being imported.
Here comes my problem: if I now try to render the component using <appAbout></appAbout>, as I have done before in web apps with vue.js, I get the following error.
It basically says that I am not using a single root element in my component, which is really strange because my component looks like this:
<template lang="html">
<div>Hello from component</div>
</template>
<script>
export default {
}
</script>
<style lang="css">
</style>
I am stuck. Can someone please help?
So I have tried a few different things with no success, like using or even as the component names.
I also have tried these two ways of starting the vue:
The way you get with electron-forge
const app = new Vue(App).$mount('#app')
and the way I learned
new Vue({el: '#app', render: h => h(App)})
Nothing seems to work...
Define your component like this :
export default {
components: {
'app-about': About
}
}
Then use it in template like this (with kebab-case) :
<app-about></app-about>
About your compiling template error you need to wrap everything in test.vue in a root element :
<template>
<div>
<h2>Hello from {{text}}</h2>
<app-about></app-about>
</div>
</template>

How to enable disable component in a complex vue app?

I am just new to vue and working on this example Notes application, it can be found below
https://coligo.io/learn-vuex-by-building-notes-app/
My question is how can I change component seen on screen right behind the toolbar. I have read basic guide but I am still behind it.
Lets say I have a button on left side call it message and have another component listing like NotesList and its called MessagesList. So, When I click message button, I want NotesList dissappear and MessageList come instead of that, and vice versa. I do not want NotesList stay as consistent on screen.
Here is App.vue and main.js files, please look in github page for mode code.
https://github.com/coligo-io/notes-app-vuejs-vuex
<template>
<div id="app">
<toolbar></toolbar>
<notes-list></notes-list>
<editor></editor>
</div>
</template>
<script>
import Toolbar from './Toolbar.vue'
import NotesList from './NotesList.vue'
import Editor from './Editor.vue'
import MessageList from './MessageList.vue'
export default {
components: {
Toolbar,
NotesList,
Editor,
MessageList
}
}
</script>
Main.js
import Vue from 'vue'
import store from './vuex/store'
import App from './components/App.vue'
new Vue({
store, // inject store to all children
el: 'body',
components: { App }
})
You could do this either by using v-if on each of the components, or by using dynamic components. In either case, your button should toggle a data value which would be used to control which component is displayed.