My Vue v3 app does not render components with vue-router#4 and nothing will show on DOM and http://localhost:8080/view does not show anything from component that correspondent to /view path.
please guide me.
I created this project with Vue CLI, and then I intalled npm install vue-router, basically i just added a new component in ./components/users and also modified the main.js file, and that's all.
import AppView from "../AppView";
import AppRegister from "../AppRegister";
import { createRouter, createWebHistory,createMemoryHistory } from 'vue-router'
const routes = [
{
path: '/view',
name: 'view',
component: AppView
},
{
path: '/register',
name: 'register',
component: AppRegister
}
]
// let history = isServer ? createMemoryHistory() : createWebHistory()
const router = createRouter({
history:createWebHistory(),
routes: routes,
linkActiveClass: 'active'
})
export default router;
import {createApp} from 'vue'
import App from './App.vue'
import router from './router/router.js'
import VueRouter from 'vue-router'
createApp(App).use(VueRouter).use(router).mount('#app')
<script>
import AppView from "./AppView.vue";
import AppRegister from "./AppRegister.vue";
export default {
name: "App",
components: {
AppView,
AppRegister
},
data() {
return {};
},
beforeCreate() {
console.log('beforeCreating...');
console.log(this.$route.query.mode);
this.$router.push('/view');
// if(typeof this.$route.query.mode !== 'undefined') {
// const redirectPath = this.$route.query.mode || "/";
// console.log("redirectPath : " + redirectPath);
// }
},
created() {
console.log('creating...');
},
mounted() {
console.log('mounting...');
}
};
</script>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: "B Titr", sans-serif;
background-image: url("./assets/Suggestion.png");
background-repeat: no-repeat;
direction: rtl;
}
.container {
max-width: 1000px;
margin: 30px auto;
overflow: auto;
min-height: 300px;
border: 1px solid steelblue;
padding: 30px;
border-radius: 5px;
}
.btn {
display: inline-block;
background: #000;
color: #fff;
border: none;
padding: 10px 20px;
margin: 5px;
border-radius: 5px;
cursor: pointer;
text-decoration: none;
font-size: 15px;
font-family: inherit;
}
.btn:focus {
outline: none;
}
.btn:active {
transform: scale(0.98);
}
.btn-block {
display: block;
width: 100%;
}
</style>
In main.js, you don't need .use(VueRouter) because the plugin initialization is already covered by the imported router instance:
import router from './router/router.js'
import VueRouter from 'vue-router'
createApp(App).use(VueRouter).use(router).mount('#app') ❌
^^^^^^^^^^^^^^^
createApp(App).use(router).mount('#app') ✅
And make sure to have a router-view in your root component (e.g., App.vue):
<template>
<router-view />
</template>
Related
How can i change the body{overflow:hidden} when my modal it will be open?
for example it will be my modal, when its open, i would like to apply this style body{overflow:hidden}
<div v-if="dialogFoundation">
i am using vuejs3, i am using setup(){...}
The best performance would be to use javascript plain. You can add Eventlistener top the modal trigger Element. In my example i use a button. If it triggered then you can use classList and assign the body a class. In my example .dark.
Vue version
<!-- Use preprocessors via the lang attribute! e.g. <template lang="pug"> -->
<template>
<div id="app">
<h1>{{message}}</h1>
<p></p>
<button #click="doSomething">Modal</button>
</div>
</template>
<script>
export default {
data() {
return {
message: 'Welcome to Vue!'
};
},
methods: {
doSomething() {
const b = document.querySelector('body');
b.classList.toggle('dark');
}
}
};
</script>
<!-- Use preprocessors via the lang attribute! e.g. <style lang="scss"> -->
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
a,
button {
color: #4fc08d;
}
button {
background: none;
border: solid 1px;
border-radius: 2em;
font: inherit;
padding: 0.75em 2em;
}
.dark {
background: black;
opacity: 0.4;
}
</style>
Vanilla JS
const btn = document.querySelector('button');
btn.addEventListener('click', () => {
const b = document.querySelector('body');
b.classList.toggle('dark');
})
.dark {
background: black;
opacity: 0.4;
}
<body>
<div></div>
<button>click</button>
</body>
You can use watchers in Vue.js for solving this problem.
When variables changes you can check whether it is true or not, and if true change overflow of body to hidden.
{
watch: {
dialogFoundation(dialogFoundation) {
document.body.style.overflow = dialogFoundation ? "hidden" : "auto"
}
}
}
But I think this is not good solution. You can set this styles to your app element
#app {
width: 100%;
height: 100%;
overflow: auto;
}
and you can change style of app element using Vue directives.
<template>
<div id="app" :class="{ hidden: dialogFoundation }">
Long text....
</div>
</template>
<script>
import { ref } from "vue";
export default {
setup() {
const dialogFoundation = ref(true);
return { dialogFoundation };
},
};
</script>
<style>
html,
body,
#app {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
box-sizing: border-box;
}
#app {
overflow: auto;
}
#app.hidden {
overflow: hidden;
}
</style>
Code in codesandbox - https://codesandbox.io/s/immutable-glitter-rwc2iy?file=/src/App.vue
MCVE
I have a Tabpane component that takes slots as input. When imported from the template it works as expected.
<Tabpane>
<div caption="I am div 1">Div 1</div>
<div caption="I am div 2">Div 2</div>
</Tabpane>
However when imported from an other component ( Composite in the example ), then it triggers the following warning:
Slot "default" invoked outside of the render function:
this will not track dependencies used in the slot. Invoke the slot function inside the render function instead.
// src/components/Composite.js
import { defineComponent, h } from "vue";
import Tabpane from "./Tabpane.vue";
export default defineComponent({
name: "Composite",
setup() {
const slots = [
h("div", { caption: "I am div 1" }, ["Div 1"]),
h("div", { caption: "I am div 2" }, ["Div 2"])
];
return () => h(Tabpane, {}, () => slots);
}
});
Solved.
The problem was that I called slots.default() from within setup, but not within the returned render function.
Also this component reflected a very beginner approach to reactivity. By now I know better. The old problematic solution is still there in src/components/Tabpane.vue.
The right solution that triggers no warning is:
// src/components/Tabpane2.vue
<script>
import { defineComponent, h, reactive } from "vue";
export default defineComponent({
name: "Tabpane2",
props: {
width: {
type: Number,
default: 400,
},
height: {
type: Number,
default: 200,
},
},
setup(props, { slots }) {
const react = reactive({
selectedTab: 0,
});
return () =>
h("div", { class: ["vertcont"] }, [
h(
"div",
{
class: ["tabs"],
},
slots.default().map((tab, i) =>
h(
"div",
{
class: {
tab: true,
selected: i === react.selectedTab,
},
onClick: () => {
react.selectedTab = i;
},
},
[tab.props.caption]
)
)
),
h(
"div",
{
class: ["slotscont"],
style: {
width: `${props.width}px`,
height: `${props.height}px`,
},
},
slots.default().map((slot, i) =>
h(
"div",
{
class: {
slot: true,
active: react.selectedTab === i,
},
},
[slot]
)
)
),
]);
},
});
</script>
<style>
.tab.selected {
background-color: #efe;
border: solid 2px #afa !important;
border-bottom: transparent !important;
}
.tab {
background-color: #eee;
}
.tabs .tab {
padding: 5px;
margin: 2px;
border: solid 2px #aaa;
border-radius: 8px;
border-bottom: transparent;
cursor: pointer;
user-select: none;
transition: all 0.5s;
color: #007;
}
.tabs {
display: flex;
align-items: center;
margin-left: 5px;
}
.vertcont {
display: flex;
flex-direction: column;
margin: 3px;
}
.slotscont {
position: relative;
overflow: scroll;
padding: 5px;
border: solid 1px #777;
}
.slot {
visibility: hidden;
position: absolute;
}
.slot.active {
visibility: visible;
}
</style>
Slots need to be invoked within the render function and or the <template> box to ensure they keep their reactivity.
A full explanation can be found in this post: https://zelig880.com/how-to-fix-slot-invoked-outside-of-the-render-function-in-vue-3
I am learning VueJS by creating a practice APP and I am stuck at a point where inside the Authors component I have a List of Authors. I want to be able to click on the list item and navigate to the AuthorDetail component, so far everything is okay. The problem arises when I use the navbar at the top to move to other view like Home or About the AuthorDetail component stays visible (it should go away!).
Code inside App.vue
<template>
<div id="nav">
<router-link to="/">Home</router-link> |
<router-link to="/authors">Authors</router-link> |
<router-link to="/about">About</router-link>
</div>
<router-view />
</template>
<style lang="scss">
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
max-width: 480px;
margin: auto;
border: 1px solid #2c3e50;
border-radius: 5px;
padding: 20px;
}
#nav {
padding: 10px;
border-bottom: 1px solid #e2e2e2;
margin-bottom: 30px;
a {
font-weight: bold;
color: #2c3e50;
&.router-link-exact-active {
color: #42b983;
}
}
}
</style>
Code in router/index.js
import { createRouter, createWebHistory } from "vue-router";
import Home from "../views/Home.vue";
import Authors from "../views/Authors.vue";
import AuthorDetail from "../views/AuthorDetail.vue";
const routes = [
{
path: "/",
name: "Home",
component: Home,
},
{
path: "/authors",
name: 'Authors',
component: Authors
},
{
path: "/authors/:id",
name: "AuthorDetail",
component: AuthorDetail
},
{
path: "/about",
name: "About",
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () =>
import(/* webpackChunkName: "about" */ "../views/About.vue"),
},
];
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes,
});
export default router;
Code inside the Authors component.
<template>
<div>
<h1>Authors</h1>
<p>Most Popular Authors (TheTestRequest API)</p>
<div class="authors-list" :key="author.id" v-for="author in authors">
<router-link :to="{ name: 'AuthorDetail', params: { id: author.id}}">
<AuthorCard #click="showAuthor(author.id)" :author="author"></AuthorCard>
</router-link>
</div>
</div>
</template>
<script>
import AuthorCard from '#/components/AuthorCard'
export default {
name: "Authors",
components: {
AuthorCard
},
data(){
return {
authors: []
}
},
methods: {
async fetchAuthors(){
const res = await fetch('https://thetestrequest.com/authors')
const data = await res.json()
return data
},
showAuthor(authorId){
console.log("Author Clicked", authorId);
}
},
async created() {
this.authors = await this.fetchAuthors()
},
}
</script>
<style lang="scss" scoped>
.authors-list {
margin-top: 2em;
// transition: box-shadow .3s;
a {
text-decoration: none;
color: black;
}
}
</style>
Note: I am using thetestrequest.com to fetch data for this practice app.
UI Samples:
Turns out there was an error in the console which helped me resolve the issue.
Error:
AuthorDetail.vue?0959:6 Uncaught (in promise) TypeError: Cannot read property 'avatar' of null
at Proxy.eval (AuthorDetail.vue?0959:6)
at renderComponentRoot (runtime-core.esm-bundler.js?5c40:1168)
at componentEffect (runtime-core.esm-bundler.js?5c40:5214)
at reactiveEffect (reactivity.esm-bundler.js?a1e9:42)
at effect (reactivity.esm-bundler.js?a1e9:17)
at setupRenderEffect (runtime-core.esm-bundler.js?5c40:5167)
at mountComponent (runtime-core.esm-bundler.js?5c40:5126)
at processComponent (runtime-core.esm-bundler.js?5c40:5084)
at patch (runtime-core.esm-bundler.js?5c40:4690)
at componentEffect (runtime-core.esm-bundler.js?5c40:5287)
So a simple check v-if="avatar" inside AuthorDetail template did the trick for me.
I'm trying to use the Amplify Chatbot Component in my application but I keep getting:
Chatbot - Bot not provided.
I've used the Amplify CLI to add Interactions, which added the correct configuration into the aws-exports.js file. I then set the Amplify.Configure to use the exports file.
But when I try to use the component in my app, I can't seem to get it to run.
App.vue
<template>
<amplify-chatbot ></amplify-chatbot>
</template>
<script>
import { Interactions } from 'aws-amplify';
export default {
name: 'App',
components: {
Interactions
},
data(){
return {
}
}
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
AWS-Exports
// WARNING: DO NOT EDIT. This file is automatically generated by AWS Amplify. It will be overwritten.
const awsmobile = {
"aws_project_region": "eu-west-1",
"aws_cognito_identity_pool_id": "eu-west-1:fbc545c0-ddac-410b-8f8d-4ba3cffadbb2",
"aws_cognito_region": "eu-west-1",
"oauth": {},
"aws_bots": "enable",
"aws_bots_config": [
{
"name": "ScheduleAppointment_dev",
"alias": "$LATEST",
"region": "eu-west-1"
}
]
};
export default awsmobile;
main.js
import Vue from 'vue'
import App from './App.vue'
import './registerServiceWorker'
import Amplify, * as AmplifyModules from 'aws-amplify'
import { AmplifyPlugin } from 'aws-amplify-vue'
import awsconfig from './aws-exports'
Amplify.configure(awsconfig)
Vue.use(AmplifyPlugin, AmplifyModules)
Vue.config.productionTip = false
new Vue({
render: h => h(App),
}).$mount('#app')
Here's a modified version of your app.vue and that will work:
<template>
<amplify-chatbot v-bind:chatbotConfig="chatbotConfig"></amplify-chatbot>
</template>
<script>
import { Interactions } from 'aws-amplify';
export default {
name: 'App',
data: () => ({
chatbotConfig:{
bot: "addTheBotNameHere",
clearComplete: true
},
}),
mounted() {
Interactions.onComplete("addTheBotNameHere", this.handleComplete);
},
methods: {
handleComplete(err,confirmation) {
if(err) {
alert("bot conversation failed");
return;
}
return "Thank You";
},
},
};
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
I have come across an issue where the implementation of slots in a webcomponent is not functioning as expected. My understanding of Web Components, Custom Elements and Slots is that elements rendered in a slot should inherit their style from the document and not the Shadow DOM however the element in the slot is actually being added to the Shadow DOM and therefore ignoring the global styles. I have created the following example to illustrate the issue that I am having.
shared-ui
This is a Vue application that is compiled to web components using the cli (--target wc --name shared-ui ./src/components/*.vue)
CollapseComponent.vue
<template>
<div :class="[$style.collapsableComponent]">
<div :class="[$style.collapsableHeader]" #click="onHeaderClick" :title="title">
<span>{{ title }}</span>
</div>
<div :class="[$style.collapsableBody]" v-if="expanded">
<slot name="body-content"></slot>
</div>
</div>
</template>
<script lang="ts">
import { Vue, Component, Prop } from 'vue-property-decorator'
#Component({})
export default class CollapsableComponent extends Vue {
#Prop({ default: "" })
title!: string;
#Prop({default: false})
startExpanded!: boolean;
private expanded: boolean = false;
constructor() {
super();
this.expanded = this.startExpanded;
}
get isVisible(): boolean {
return this.expanded;
}
onHeaderClick(): void {
this.toggle();
}
public toggle(expand?: boolean): void {
if(expand === undefined) {
this.expanded = !this.expanded;
}
else {
this.expanded = expand;
}
this.$emit(this.expanded? 'expand' : 'collapse');
}
public expand() {
this.expanded = true;
}
public collapse() {
this.expanded = false;
}
}
</script>
<style module>
:host {
display: block;
}
.collapsableComponent {
background-color: white;
}
.collapsableHeader {
border: 1px solid grey;
background: grey;
height: 35px;
color: black;
border-radius: 15px 15px 0 0;
text-align: left;
font-weight: bold;
line-height: 35px;
font-size: 0.9rem;
padding-left: 1em;
}
.collapsableBody {
border: 1px solid black;
border-top: 0;
border-radius: 0 0 10px 10px;
padding: 1em;
}
</style>
shared-ui-consumer
This is a vue application that imports the shared-ui web component using a standard script include file.
App.vue
<template>
<div id="app">
<shared-ui title="Test">
<span class="testClass" slot="body-content">
Here is some text
</span>
</shared-ui>
</div>
</template>
<script lang="ts">
import 'vue'
import { Component, Vue } from 'vue-property-decorator';
#Component({ })
export default class App extends Vue {
}
</script>
<style lang="scss">
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
.testClass{
color: red;
}
</style>
main.ts
import Vue from "vue";
import App from "./App.vue";
Vue.config.productionTip = false;
// I needed to do this so the web component could reference Vue
(window as any).Vue = Vue;
new Vue({
render: h => h(App),
}).$mount('#app');
In this example I would expect the content inside the container to have red text however because Vue is cloning the element into the Shadow DOM the .testClass style is being ignored and the text is rendered with a black fill.
How can I apply .testClass to the element inside of my web component?
Ok, so I managed to find a workaround for this that uses native slots and renders the child components correctly in the correct place in the DOM.
In the mounted event wire up the next tick to replace the innerHtml of your slot container with a new slot. You can get fancy and do some cool replacements for named slots and whatnot but this should suffice for illustrating the workaround.
shared-ui
This is a Vue application that is compiled to web components using the cli (--target wc --name shared-ui ./src/components/*.vue)
CollapseComponent.vue
<template>
<div :class="[$style.collapsableComponent]">
<div :class="[$style.collapsableHeader]" #click="onHeaderClick" :title="title">
<span>{{ title }}</span>
</div>
<div ref="slotContainer" :class="[$style.collapsableBody]" v-if="expanded">
<slot></slot>
</div>
</div>
</template>
<script lang="ts">
import { Vue, Component, Prop } from 'vue-property-decorator'
#Component({})
export default class CollapsableComponent extends Vue {
#Prop({ default: "" })
title!: string;
#Prop({default: false})
startExpanded!: boolean;
private expanded: boolean = false;
constructor() {
super();
this.expanded = this.startExpanded;
}
get isVisible(): boolean {
return this.expanded;
}
onHeaderClick(): void {
this.toggle();
}
//This is where the magic is wired up
mounted(): void {
this.$nextTick().then(this.fixSlot.bind(this));
}
// This is where the magic happens
fixSlot(): void {
// remove all the innerHTML that vue has place where the slot should be
this.$refs.slotContainer.innerHTML = '';
// replace it with a new slot, if you are using named slot you can just add attributes to the slot
this.$refs.slotContainer.append(document.createElement('slot'));
}
public toggle(expand?: boolean): void {
if(expand === undefined) {
this.expanded = !this.expanded;
}
else {
this.expanded = expand;
}
this.$emit(this.expanded? 'expand' : 'collapse');
}
public expand() {
this.expanded = true;
}
public collapse() {
this.expanded = false;
}
}
</script>
<style module>
:host {
display: block;
}
.collapsableComponent {
background-color: white;
}
.collapsableHeader {
border: 1px solid grey;
background: grey;
height: 35px;
color: black;
border-radius: 15px 15px 0 0;
text-align: left;
font-weight: bold;
line-height: 35px;
font-size: 0.9rem;
padding-left: 1em;
}
.collapsableBody {
border: 1px solid black;
border-top: 0;
border-radius: 0 0 10px 10px;
padding: 1em;
}
</style>
shared-ui-consumer
This is a vue application that imports the shared-ui web component using a standard script include file.
App.vue
<template>
<div id="app">
<shared-ui title="Test">
<span class="testClass" slot="body-content">
Here is some text
</span>
</shared-ui>
</div>
</template>
<script lang="ts">
import 'vue'
import { Component, Vue } from 'vue-property-decorator';
#Component({ })
export default class App extends Vue {
}
</script>
<style lang="scss">
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
.testClass{
color: red;
}
</style>
main.ts
import Vue from "vue";
import App from "./App.vue";
Vue.config.productionTip = false;
// I needed to do this so the web component could reference Vue
(window as any).Vue = Vue;
new Vue({
render: h => h(App),
}).$mount('#app');