have to add 3 components to one page - vue.js

In my project, I have 3 components. one component is already showing on the page and now I want to add those 2 components with that component. The code is in below,
<template>
<component v-bind:is="currentComponent" ></component>
</template>
<script>
import { ROAST_CONFIG } from '../../../config/config.js';
import ZoneIndex from './components/zone/Index';
import { listen } from '../../../util/history.js';;
import axios from 'axios'
let baseUrl = ROAST_CONFIG.API_URL;
export default {
name: 'LocationsView',
layout: 'admin/layouts/default/defaultLayout',
middleware: 'auth',
components: {
'zone-index' : ZoneIndex,
},
data() {
return { currentComponent:'','stateId':''}
},
methods: {
updateCurrentComponent(){
console.log(this.$route.name);
let vm = this;
let route = vm.$route;
if(this.$route.name == "Locations"){
this.currentComponent = "zone-index";
}
}
},
mounted() {
let vm = this;
let route = this.$route;
window.addEventListener('popstate',this.updateCurrentComponent);
},
created() {
this.updateCurrentComponent();
}
}
The ZoneIndex component is showing in the code. The other 2 components are CountryIndex and StateIndex.

The correct way to carry out your procedure would be.
<template>
<div>
<zone-index></zone-index>
<state-index></state-index>
<country-index></country-index>
</div>
</template>
<script>
import ZoneIndex from './components/zone/Index';
import CountryIndex from '...way';
import StateIndex ryIndex from '...way';
import { ROAST_CONFIG } from '../../../config/config.js';
import { listen } from '../../../util/history.js';;
import axios from 'axios'
let baseUrl = ROAST_CONFIG.API_URL;
export default {
name: 'LocationsView',
layout: 'admin/layouts/default/defaultLayout',
middleware: 'auth',
components: { ZoneIndex, CountryIndex, StateIndex },
data() {
return { currentComponent:'','stateId':''}
},
methods: {
updateCurrentComponent(){
console.log(this.$route.name);
let vm = this;
let route = vm.$route;
if(this.$route.name == "Locations"){
this.currentComponent = "zone-index";
}
}
},
mounted() {
let vm = this;
let route = this.$route;
window.addEventListener('popstate',this.updateCurrentComponent);
},
created() {
this.updateCurrentComponent();
}
}

Related

How can I pass the arguments to the NUXT component in Storybook?

I've got a NUXT/VUE component which does an API call and assigns the result to the data, therefore template maps the result to the page. Simple API call.
I am just not sure how I can do that in Storybook component?
Do I have to mock the API fetch or pass the static data to the component in the Storybook?
Examples on the the official website is all about props, nothing about data
https://storybook.js.org/docs/react/writing-stories/args
Here is my simple component
<template>
<div>{{blogPosts.title}}</div>
</template>
<script>
export default {
data() {
return {
blogPosts: [],
};
},
async fetch() {
this.blogPosts = await this.$http.$get("https://api.nuxtjs.dev/posts");
},
};
</script>
Here is my Storybook Component:
import { Meta, Story } from "#storybook/vue";
import BlogCarousel from "./BlogCarousel.vue";
import { BlogPost } from "~/lib/types/BlogPost";
export default {
title: "BlogCarousel",
components: BlogCarousel,
} as Meta;
const Template: Story<BlogPost> = (args) => {
return {
components: { BlogCarousel },
template: `<BlogCarousel v-bind=${args.blogPosts} />`,
};
};
export const Default = Template.bind({});
Default.args = {
blogPosts: [
{
title: "test",
created: "today",
},
],
};

Critical dependency: require function in nuxt project

I am recieving "Critical dependency: require function is used in a way in which dependencies cannot be statically extracted friendly-errors 16:21:14" error when using the package scrollMonitor in my nuxt project
plugins/scroll-monitor.js
import Vue from 'vue';
// your imported custom plugin or in this scenario the 'vue-session' plugin
import ScrollMonitor from 'scrollmonitor';
Vue.use(ScrollMonitor);
nuxt.config.js
plugins: [
'~/plugins/wordpress-api',
{ src: '~/plugins/scroll-monitor.js', ssr: false }
],
build: {
/*
** You can extend webpack config here
*/
vendor: ['scrollmonitor'],
extend(config, ctx) {
}
}
At my index.vue file
let scrollmonitor
if (process.client) {
scrollmonitor = require('scrollmonitor')
}
More context
Still not working.
I am using new computer, at my old one everything is working fine.
index.vue
<template>
<div class="index page-padding-top">
<TheHero
:scaledUpDot="scaledUpDot"
:isFirstImageVisible="isFirstImageVisible"
/>
<ProjectsList :projects="projects" />
</div>
</template>
<script>
import { mapGetters } from "vuex";
import TheHero from "~/components/TheHero";
import ProjectsList from "~/components/ProjectsList";
export default {
async mounted () {
if (process.browser) {
const scrollMonitor = await import('scrollmonitor')
Vue.use(scrollMonitor)
console.log('HELLO FROM MOUNTED')
}
},
name: "Index",
components: { TheHero, ProjectsList},
data() {
return {
scaledUpDot: false,
isFirstImageVisible: false,
};
},
computed: {
...mapGetters({
projects: "getProjects",
}),
},
mounted() {
this.handleScaling();
this.hideScrollSpan();
},
destroyed() {
this.handleScaling();
this.hideScrollSpan();
},
methods: {
handleScaling() {
if (process.client) {
const heroSection = document.querySelectorAll(".hero");
const heroSectionWtcher = scrollMonitor.create(heroSection, 0);
heroSectionWtcher.enterViewport(() => {
this.scaledUpDot = true;
});
}
},
hideScrollSpan() {
if (process.client) {
const images = document.querySelectorAll(".projects-home img");
const firstImage = images[0];
const imageWatcher = scrollMonitor.create(firstImage, -30);
imageWatcher.enterViewport(() => {
this.isFirstImageVisible = true;
});
}
},
},
};
</script>
In my old computer I have it imported like this :
import { mapGetters } from 'vuex'
import scrollMonitor from 'scrollmonitor'
But when I want to run this in a new one I get an error that window is not defined
So I have started to add this plugin in other way and still not working
Still not working.
I am using new computer, at my old one everything is working fine.
index.vue
<template>
<div class="index page-padding-top">
<TheHero
:scaledUpDot="scaledUpDot"
:isFirstImageVisible="isFirstImageVisible"
/>
<ProjectsList :projects="projects" />
</div>
</template>
<script>
import { mapGetters } from "vuex";
import TheHero from "~/components/TheHero";
import ProjectsList from "~/components/ProjectsList";
export default {
async mounted () {
if (process.browser) {
const scrollMonitor = await import('scrollmonitor')
Vue.use(scrollMonitor)
console.log('HELLO FROM MOUNTED')
}
},
name: "Index",
components: { TheHero, ProjectsList},
data() {
return {
scaledUpDot: false,
isFirstImageVisible: false,
};
},
computed: {
...mapGetters({
projects: "getProjects",
}),
},
mounted() {
this.handleScaling();
this.hideScrollSpan();
},
destroyed() {
this.handleScaling();
this.hideScrollSpan();
},
methods: {
handleScaling() {
if (process.client) {
const heroSection = document.querySelectorAll(".hero");
const heroSectionWtcher = scrollMonitor.create(heroSection, 0);
heroSectionWtcher.enterViewport(() => {
this.scaledUpDot = true;
});
}
},
hideScrollSpan() {
if (process.client) {
const images = document.querySelectorAll(".projects-home img");
const firstImage = images[0];
const imageWatcher = scrollMonitor.create(firstImage, -30);
imageWatcher.enterViewport(() => {
this.isFirstImageVisible = true;
});
}
},
},
};
</script>
In my old computer I have it imported like this :
import { mapGetters } from 'vuex'
import scrollMonitor from 'scrollmonitor'
But when I want to run this in a new one I get an error that window is not defined
So I have started to add this plugin in other way and still not working

Vue3 reactive components on globalProperties

In vuejs 2 it's possible to assign components to global variables on the main app instance like this...
const app = new Vue({});
Vue.use({
install(Vue) {
Vue.prototype.$counter = new Vue({
data: () => ({ value: 1 }),
methods: {
increment() { this.value++ },
}
});
}
})
app.$mount('#app');
But when I convert that to vue3 I can't access any of the properties or methods...
const app = Vue.createApp({});
app.use({
install(app) {
app.config.globalProperties.$counter = Vue.createApp({
data: () => ({ value: 1 }),
methods: {
increment() { this.value++ }
}
});
}
})
app.mount('#app');
Here is an example for vue2... https://jsfiddle.net/Lg49anzh/
And here is the vue3 version... https://jsfiddle.net/Lathvj29/
So I'm wondering if and how this is still possible in vue3 or do i need to refactor all my plugins?
I tried to keep the example as simple as possible to illustrate the problem but if you need more information just let me know.
Vue.createApp() creates an application instance, which is separate from the root component of the application.
A quick fix is to mount the application instance to get the root component:
import { createApp } from 'vue';
app.config.globalProperties.$counter = createApp({
data: () => ({ value: 1 }),
methods: {
increment() { this.value++ }
}
}).mount(document.createElement('div')); 👈
demo 1
However, a more idiomatic and simpler solution is to use a ref:
import { ref } from 'vue';
const counter = ref(1);
app.config.globalProperties.$counter = {
value: counter,
increment() { counter.value++ }
};
demo 2
Not an exact answer to the question but related. Here is a simple way of sharing global vars between components.
In my main app file I added the variable $navigationProps to global scrope:
let app=createApp(App)
app.config.globalProperties.$navigationProps = {mobileMenuClosed: false, closeIconHidden:false };
app.use(router)
app.mount('#app')
Then in any component where I needed that $navigationProps to work with 2 way binding:
<script>
import { defineComponent, getCurrentInstance } from "vue";
export default defineComponent({
data: () => ({
navigationProps:
getCurrentInstance().appContext.config.globalProperties.$navigationProps,
}),
methods: {
toggleMobileMenu(event) {
this.navigationProps.mobileMenuClosed =
!this.navigationProps.mobileMenuClosed;
},
hideMobileMenu(event) {
this.navigationProps.mobileMenuClosed = true;
},
},
Worked like a charm for me.
The above technique worked for me to make global components (with only one instance in the root component). For example, components like Loaders or Alerts are good examples.
Loader.vue
...
mounted() {
const currentInstance = getCurrentInstance();
if (currentInstance) {
currentInstance.appContext.config.globalProperties.$loader = this;
}
},
...
AlertMessage.vue
...
mounted() {
const currentInstance = getCurrentInstance();
if (currentInstance) {
currentInstance.appContext.config.globalProperties.$alert = this;
}
},
...
So, in the root component of your app, you have to instance your global components, as shown:
App.vue
<template>
<v-app id="allPageView">
<router-view name="allPageView" v-slot="{Component}">
<transition :name="$router.currentRoute.name">
<component :is="Component"/>
</transition>
</router-view>
<alert-message/> //here
<loader/> //here
</v-app>
</template>
<script lang="ts">
import AlertMessage from './components/Utilities/Alerts/AlertMessage.vue';
import Loader from './components/Utilities/Loaders/Loader.vue';
export default {
name: 'App',
components: { AlertMessage, Loader }
};
</script>
Finally, in this way you can your component in whatever other components, for example:
Login.vue
...
async login() {
if (await this.isFormValid(this.$refs.loginObserver as FormContext)) {
this.$loader.activate('Logging in. . .');
Meteor.loginWithPassword(this.user.userOrEmail, this.user.password, (err: Meteor.Error | any) => {
this.$loader.deactivate();
if (err) {
console.error('Error in login: ', err);
if (err.error === '403') {
this.$alert.showAlertFull('mdi-close-circle', 'warning', err.reason,
'', 5000, 'center', 'bottom');
} else {
this.$alert.showAlertFull('mdi-close-circle', 'error', 'Incorrect credentials');
}
this.authError(err.error);
this.error = true;
} else {
this.successLogin();
}
});
...
In this way, you can avoid importing those components in every component.

Only add linkExactActiveClass in specific layout and page

I'm relatively new to Vue. I have a multi-layout on my app, but only one router.
I followed this tutorial for context. I want to only add active classes on a specific layout and page. For example, I only want active classes on the admin layout navigation, not on the landing page layout navigation. How do I achieve this?
main.js
import { createApp } from "vue";
import App from "./App.vue";
import { routes } from "./routes.js";
import { createRouter, createWebHistory } from "vue-router";
const app = createApp(App);
const router = createRouter({
history: createWebHistory(),
routes,
linkExactActiveClass: "active",
});
app.use(router);
app.mount("#app");
routes.js
import Home from "./views/Home.vue";
import Dashboard from "./views/app/Dashboard.vue";
export const routes = [
{
path: "/",
component: Home,
meta: { layout: "LandingLayout", title: "Home" },
},
{
path: "/user",
component: Dashboard,
meta: { layout: "AppLayout", title: "Dashboard" },
},
];
App.vue
<template>
<component :is="layout" />
</template>
<script>
import LandingLayout from "#/layouts/LandingLayout.vue";
import AdminLayout from "#/layouts/AdminLayout.vue";
export default {
components: {
LandingLayout,
AdminLayout,
},
data() {
return {
layout: null,
};
},
watch: {
$route(to) {
if (to.meta.layout !== undefined) {
this.layout = to.meta.layout;
} else {
this.layout = "LandingLayout";
}
},
},
};
</script>
Any help would be much appreciated.
You were pretty close. The only thing you forgot was to make the route watch happen immediately by providing it like this:
watch: {
$route: {
immediate: true,
handler(to) {
if (to.meta?.layout) {
this.layout = to.meta.layout;
} else {
this.layout = "LandingLayout";
}
},
},
},
and then you can just change the this.$router.options.linkExactActiveClass option dynamically like this:
$route: {
immediate: true,
handler(to) {
if (to.meta?.layout) {
this.layout = to.meta.layout;
this.$router.options.linkExactActiveClass = `my-active-link-other-layout`;
} else {
this.layout = "LandingLayout";
this.$router.options.linkExactActiveClass =
"my-active-link-landing-layout";
}
},
},
See it in action:
Codesandbox link

Rollup Vue 3 Dynamic img src

Dynamic img src were handled by Webpack's require:
<img :src="require(`#/assets/${posts.img}`)" alt="">
How to do this on a vite-app that uses Rollup?
you can refer to docs of webpack-to-vite:
use Vite's API import.meta.glob to convert dynamic require(e.g. require('#assets/images/' + options.src)), you can refer to the following steps
create a Model to save the imported modules, use async methods to dynamically import the modules and update them to the Model
// src/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
const assets = import.meta.glob('../assets/**')
Vue.use(Vuex)
export default new Vuex.Store({
state: {
assets: {}
},
mutations: {
setAssets(state, data) {
state.assets = Object.assign({}, state.assets, data)
}
},
actions: {
async getAssets({ commit }, url) {
const getAsset = assets[url]
if (!getAsset) {
commit('setAssets', { [url]: ''})
} else {
const asset = await getAsset()
commit('setAssets', { [url]: asset.default })
}
}
}
})
use in .vue SFC
// img1.vue
<template>
<img :src="$store.state.assets['../assets/images/' + options.src]" />
</template>
<script>
export default {
name: "img1",
props: {
options: Object
},
watch: {
'options.src': {
handler (val) {
this.$store.dispatch('getAssets', `../assets/images/${val}`)
},
immediate: true,
deep: true
}
}
}
</script>