Nuxt3 - NuxtLink to anchor not scrolling on click or page refresh - vue.js

I'm simply trying to have a page scroll to an anchor point in Nuxt3 and nothing I can do will get it to work. It doesn't scroll on click, or on page refresh with the anchor in the url.
<nuxt-link :to="{path: '/', hash: '#projects'}">Let's go</nuxt-link>
Tried a bunch of other SO answers, adding custom scrollBehaviour code to the nuxtConfig didn't work and trying to install vue-scrollTo in Nuxt3 just gave me this error when running the app with the vue-scrollTo module
ERROR Cannot restart nuxt: serialize is not defined
Any help would be greatly appreciated!
Full code
<script setup>
import '#/assets/css/main.css';
const { data } = await useAsyncData('home', () => queryContent('/').find())
const projects = data
</script>
<template>
<div>
<div class="flex flex-col h-screen">
<div class="lg:p-20 p-10 text-white bg-orange-500">
<p class="font-playfair lg:text-7xl text-4xl mb-5">Kia Ora, my name is <span class="font-medium italic">George Bates</span></p>
<p class="font-lato lg:text-3xl text-xl mb-5">Content creator and web developer in Auckland, New Zealand</p>
</div>
<div class="lg:p-20 p-10 text-white flex flex-grow" style="background-image: url('images/header.jpg'); background-position: center; background-size: cover;">
<nuxt-link :to="{path: '/', hash: '#projects'}">Let's go</nuxt-link>
</div>
</div>
<main class="lg:p-20 p-10" id="projects">
<p class="text-3xl font-playfair mb-5 font-semibold pb-2 text-orange-500">Some of my work</p>
<Projects :projects="projects" />
</main>
</div>
</template>

You said that you already tried to add a custom scrollBehavior, but how did you do that?
I'm very new to Vue & Nuxt, but thanks to this Github answer, you can customize the scroll behavior, and this works for me:
File app/route.options.ts:
import type { RouterConfig } from '#nuxt/schema';
// https://router.vuejs.org/api/#routeroptions
export default <RouterConfig>{
scrollBehavior(to, from, savedPosition) {
return {
el: to.hash,
behavior: 'smooth',
};
},
};
(Here I put a smooth behavior, but default seems to be auto)
And with a sample of code like:
...
<NuxtLink :to="{path: '/', hash: '#anchor'}">Let's go!</NuxtLink>
...

Related

Global CSS doesn’t work when programmatically change route

My index page has a Header and Home components.
{
path: "/",
name: "Homepage",
components: {
header: () => import("../components/layout/HomepageHeader.vue"),
main: () => import("../views/Home.vue"),
},
},
Both don’t have any scoped CSS, they only use Tailwind CSS classes. And that Tailwind CSS file is global.
The problem occurs when I programmatically redirect to my index page (for example, after I logged in). The HomepageHeader component doesn’t have all the CSS. Same with the Home.vue component.
Here’s the simplified code of the Header
<template>
<div class="w-full bg-white sm:shadow-lg mb-6">
<div class="mx-6">
<nav
class="px-3 flex flex-col sm:flex-row sm:px-4 sm:justify-between sm:h-16 list-none"
>
<li
class="text-center h-16 font-semibold text-red-500 text-lg sm:text-xl sm:h-16 table"
>
<div class=" py-4 sm:h-16">
<router-link to="/">Home</router-link>
</div>
</li>
<li>
<login-badge
class="shadow-lg md:shadow-none px-4 py-3 flex sm:px-0 sm:h-14 text-white"
></login-badge>
</li>
</nav>
</div>
</div>
</template>
When I say
“component doesn’t have all the CSS”
I mean that all the classes in the HomepageHeader.vue component don’t work. But the classes inside the child component LoginBadge.vue work fine!
What else can I say? I use router.replace("/"); to redirect to the homepage.
How do I fix that?
P.S. Just found out that the same thing happends when I go back to a previous page and that previous page is the index page.
P.P.S. global CSS is imported inside the App.vue
<template>
<router-view name="header"></router-view>
<main class="container mx-auto">
<router-view name="main"></router-view>
</main>
</template>
<script>
export default {
setup() {
return {};
},
};
</script>
<style>
#import "./assets/tailwind.css";

Passing props to vue instance from .blade.php file using createElement and render function

EDIT: Some more information:
I have a main-page.blade.php file, with this:
//main-page.blade.php where vue instance will mount
<div id="main-page" class=" bg-uoi-green">
<main-page :data=['one'] ></main-page>
</div>
There is also a main.js file importing the app.js file of Vue, where the instance for main-page is defined ( I have installed vue-loader and template compiler).
The app.js for Vue is:
//app.js of Vue
import Vue from 'vue'
import './assets/tailwind.css'
//
// import MainPage from './components/MainPage.vue'
// const mainpage_inst=new Vue({
// el: '#main-page',
// components: {
// MainPage,
// },
// props: ['data'],
// });
Vue.config.productionTip = false
export default {
mainpage_inst,
}
Compiling for my application was giving me exactly what was needed. But then, if I try to also check the Vue directory using vue client, I get an empty page; this was to be expected since the vue-cli does not use the full-build. But even when I changed
// import Vue from 'vue'
to
import Vue from 'vue/dist/vue.js';
I was getting a blank page when visiting localhost:8080 from vue-cli.
I was able to fix it by using the render function of vue. So now, the instance became:
import MainPage from './components/MainPage.vue'
// Main page instance
const mainpage_inst = new Vue({
render: h => h(MainPage),
}).$mount('#main-page')
But, no matter how I try to pass props as before, my main application cannot see them, in contrast to the previous writing where it was able, but vue-cli was producing a blank page.
For clarification: I have a Vue-app inside a different app that is loaded with all the required( I think) modules to read and compile .vue files. But I would also like to be able to run the Vue-app using the vue-cli.
So, is my main-app missing a module and is not able to get the props? Or is there some other way to make 'understand'?
Thanks again.
In the past, I was using the following code to create the application instance :
// const mainpage_inst=new Vue({
// el: '#main-page',
// components: {
// MainPage,
// },
// props: ['data'],
// });
and I was calling the MainPage component from a .blade.php file by passing the props:
<main-page :data=" {{ $data_to_send_as_props }}" > </main-page>
Now I want to use the following code:
import MainPage from './components/MainPage.vue'
// Main page instance
const mainpage_inst = new Vue({
render: h => h(MainPage),
}).$mount('#main-page')
but if I try to define the props as before, the application does not recognizes them. What is the correct way to write the above excerpt of code, so that props will be passed ? In other words, how and were should I add the props definition on the instance above (note also that I should implement also the change mentioned in the answer of Daniel)
Edit:
This is a simplified version of the MainPage.vue component
<template>
<div class=" h-screen w-screen md:text-2xl text:2xl " id="main-page ">
<div class=" flex flex-shrink text-4xl md:text-9xl font-bold md:ml-20 md:my-5 md:py-0 py-10 title">
A Title
</div>
<div class="flex flex-col flex-grow h-1/3 ">
<div v-for="(item,index) in slicedArray " v-bind:key="item.id" >
<div class=" flex flex-col md:flex-row md:flex-grow w-full hover:bg-uoi-green px-10 border-black border-2 transition duration-500 cursor-pointer ease-in-out bg-opacity-70 transform hover:bg-opacity-95" :class="{active: isActive === item.id}">
<div class=" date-font h-full w-1/6 md:flex-grow px-2 py-4"> {{item.date}} — </div>
<div class=" h-full w-2/3 md:flex-grow px-2 py-4 text-left"> {{item.title}} </div>
<div class=" date-font h-full w-1/6 md:flex-grow px-2 py-4 text-right"> {{item.exp_date}} </div>
</div>
<div class=" bg-white py-10 px-20" v-if="isActive === item.id">
<div > {{item.text}} </div>
<div class='file'> <a href=''> Σχετικό αρχείο </a>
<!-- <img src="../assets/curved-up-arrow-.png" class="arrow"/> -->
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name:'MainPage',
props: ['data'],
data(){
return{
isActive: null,
newsToShow: 3,
totalnews: 0,
array:[],
slicedArray:[],
}
},
mounted(){
this.newsToShow=3;
this.array=this.data;
this.totalnews = this.array.length;
// console.log(this.array)
this.slicedArray=this.array.slice(0,this.newsToShow)
},
}
</script>
<style scoped>
</style>
One of the breaking changes in Vue 3 was that the Global Vue API was changed to use an application instance. To create a vue app, use the createApp function to create the instance.
a-new-global-api-createapp
The render function (h) is not provided by the render function, so you need to import it. docs
so your instantiation should look something like this:
import { h, createApp } from 'vue'
import MainPage from './components/MainPage.vue'
// Main page instance
const mainpage_inst = new createApp({
render: () => h(MainPage),
}).$mount('#main-page')

Vue how to customize global navbar at view level

Im super new to Vue.
i have a Vue-CLI app, which have a navbar and content.
Navbar is common to all pages, but i want to customize in each page whit some additional content.
Example:
Common-> home | about
View home -> home | about | your are in view home
View about -> home | about | your are in view about
router/index.js
import Vue from 'vue';
import VueRouter from 'vue-router';
import Home from '../views/Home.vue';
import NavBar from '#/components/NavBar.vue';
Vue.use(VueRouter);
Vue.component('nav-bar', NavBar);
//...
components/navbar.vue
<template>
<div>
<b-nav-item to="/">home</b-nav-item>
<b-nav-item to="/about">about</b-nav-item>
{{customContent}}
</div>
</template>
<script>
export default {
name: 'NavBar',
props: {
customContent: {
type: String,
default: 'default Content',
},
},
};
</script>
App.vue
<template>
<div id="app">
<nav-bar />
<div class="container-fluid">
<router-view />
</div>
</div>
</template>
views/home.vue
<template>
<div class="row">
<div class="col-12">
<image-card :images="images"/>
</div>
</div>
</template>
<script>
//how can i customize here the navbar by adding for example 'your are in view home'???
</script>
Thanks so much!
There are a few ways in which you can solve this problem. I'll list two of them.
1. Update NavBar by $route
In this approach, the NavBar component already contains all of the possible combinations, and will display the relevant portion(s) depending on what $route contains.
Here's some pseudo code:
navbar.vue
<template>
<div class="navbar">
<div class="navbar-left>
APPNAME
</div>
<div v-if="name === 'landing'">
...
</div>
<div v-else-if="name === 'room'">
...
</div>
</div>
</template>
App.vue
<template>
<div id="app">
<NavBar :name="$route.name"/>
<main>
<router-view/>
</main>
</div>
</template>
In this example, the NavBar component is very rigid, and doesn't really lend itself to much reuse. However, it does encapsulate all the relevant code relating to the nav bar.
2. Extensible NavBar with slots
In this approach, the NavBar only provides the bare-minimum to create a nav bar. The rest of the route-specific elements are to be filled in by the views.
navbar.vue
<template>
<div class="navbar">
<div class="navbar-left">
<div class="navbar-brand">
APPNAME
</div>
<slot name="left"></slot>
</div>
<div class="navbar-right">
<slot name="right"></slot>
</div>
</div>
</template>
App.vue
<template>
<div id="app">
<router-view/>
</div>
</template>
landing.vue
<template>
<div>
<header>
<NavBar>
<template slot="right">
<span>
<div class="navbar-item">
<div class="buttons">
<button class="button" #click="...">Start Watching</button>
</div>
</div>
</span>
</template>
</NavBar>
</header>
<main>
...
</main>
</div>
</template>
This approach has a bit of repetition in terms of DOM elements, but gives you an extremely flexible NavBar that can be customized by each view.
The approach you want to use depends on what is important to you.
If strict encapsulation is what you want, then you may want to use approach 1, as all of the NavBar-related code is contained within a single file.
However, if you believe that there is a potential for reuse, or if you would like all view-related code to live in one place, then it makes sense to use slots instead and extend the NavBar as required by each view.
I use a breadcrumb to achieve a similar thing. Just an idea but Vue router allows you to add meta data to the current route which you always have access to
router.js
path: '/add',
name: 'add',
component: () => import(/* webpackChunkName: "add" */ '../../views/Add.vue'),
meta: {
breadCrumb: [
{ name: 'Add New' }
]
},
Notice the meta object attached to the route.. this will be used to describe the current view.
Breadcrumb.vue component
<template>
<div class="breadcrumb">
<ul class="d-flex m-0 p-0"
<li
v-for="(breadcrumb, idx) in breadcrumbList"
:key="idx">
{{ breadcrumb.name }}
</li>
</ul>
</div>
</template>
<script>
export default {
name: 'Breadcrumb',
data () {
return {
breadcrumbList: []
}
},
mounted () { this.updateList() },
watch: { '$route' () { this.updateList() } },
methods: {
routeTo (pRouteTo) {
if (this.breadcrumbList[pRouteTo].link) this.$router.push(this.breadcrumbList[pRouteTo].link)
},
updateList () { this.breadcrumbList = this.$route.meta.breadCrumb },
formatPath(path) {
const newPath = path.replace(/\//g, " > ")
return newPath
}
}
}
</script>
And then you can import the breadcrumb into your navbar or where ever you would like to place it
<Breadcrumb class="breadcrumb" />
import Breadcrumb from '#/components/Breadcrumb.vue'
components: {Breadcrumb}
So basically the breadcrumb will always watch your current route and change the data based on the meta data you provide in your router.js file
You can access to router name like this:
<div v-if="this.$route.name == 'home'">
<HeaderTransparent />
</div>
<div v-else>
<HeaderWhite />
</div>

bootstrap-vue v-tabs inside v-card renders 'undefined'

Using bootstrap-vue I have a working example using b-tabs and some other of it's components.
However, on one page (.vue) when I try to wrap the block within a tag the page always renders that card and its contents as 'undefined'
I use the sample from here.
Without the v-card I see:
and with it I see:
The code literally follows the sample referenced in the page above and in my main.ts I have the following
import {Card} from 'bootstrap-vue/es/components'
import { Button } from 'bootstrap-vue/es/components';
import { Collapse } from 'bootstrap-vue/es/components';
import { Alert } from 'bootstrap-vue/es/components';
import { Tabs } from 'bootstrap-vue/es/components';
Vue.use(Tabs);
Vue.use(Alert);
Vue.use(Collapse);
Vue.use(Button);
Vue.use(Card);
new Vue({
router,
store,
components: {
bCard: Card,
bButton: Button,
bCollapse: Collapse,
bAlert: Alert,
bTabs: Tabs,
pagination
},
render: h => h(App)
}).$mount("#app");
Can anyone point me in the right direction even to see how to isolate any underlying problem please?
EXTRA CODE ADDED BELOW
Vue (lang="ts") component which does not display correctly:
<template>
<div class="container-fluid">
<div class="row ml-1">
<h4>Complaint Reference: {{ complaint.ComplaintReference }}</h4>
<div class="col-12"><p>Enter the details of the complaint and click Save</p></div>
<div class="col-12">
<b-alert variant="success"
dismissible
:show="showDismissableAlert"
#dismissed="showDismissableAlert=false">
Your changes have been saved
</b-alert>
</div>
</div>
<div class="row">
<!-- the next line is a separate vue component that uses the same approach and which renders correctly -->
<tabs-view></tabs-view>
<div class="col-md-12">
<b-card no-body> <!-- THIS IS WHAT BREAKS! -->
<b-tabs card>
<b-tab title="Details" active>
<p> in first tab</p>
</b-tab>
<b-tab title="Contact">
<p> in second tab</p>
</b-tab>
<b-tab title="Description">
<p> in third tab</p>
</b-tab>
<b-tab title="Outcome">
<p> in final tab</p>
</b-tab>
</b-tabs>
</b-card> <!-- THIS IS WHAT BREAKS! -->
</div>
</div>
<div class="clearfix"></div>
</div>
</template>
<script lang="ts">
import {Component, Prop, Vue} from "vue-property-decorator";
import {Complaint} from "#/components/complaints/Complaint";
import TabsView from '../shared/Tabs.vue'
#Component({
components: {
tabsView: TabsView
}
})
export default class EditComplaintComponent extends Vue {
complaint: Complaint = new Complaint("");
baseUri: string = "api/Complaints/GetByReference/?id=";
showDismissableAlert: Boolean = false;
}
</script>
<style scoped>
</style>
and then another non-TS vue component consumed on the broken one which works correctly with b-tabs inside b-card
<template>
<div>
<b-card title="Card Title"
img-src="https://lorempixel.com/600/300/food/5/"
img-alt="Image"
img-top
tag="article"
style="max-width: 20rem;"
class="mb-2">
<!--<p class="card-text">-->
<!--Some quick example text to build on the card title and make up the bulk of the card's content.-->
<!--</p>-->
<b-button href="#" variant="primary">Go somewhere</b-button>
<b-card no-body>
<b-tabs card>
<b-tab title="first" active>
<br>I'm the first fading tab
</b-tab>
<b-tab title="second" >
<br>I'm the second tab content
</b-tab>
<b-tab title="disabled" disabled>
<br>Disabled tab!
</b-tab>
</b-tabs >
</b-card>
</b-card>
</div>
</template>
<script>
export default {
components: { }
}
</script>
<style scoped>
</style>
I ran into a similar issue with bootstrap-vue 2.21.2. Ended up being that I had a component that wasn't marked up with #Component. After adding it in all my components went from 'undefined' text to display the proper content. See https://github.com/bootstrap-vue/bootstrap-vue/issues/5985#issuecomment-765558938.
Upgrade to the latest version of BootstrapVue to get around this bug.
I had a parent component missing the #Component decorator (in my case it was App.vue) that was causing the undefined behaviour. Make sure that all components have this decorator applied.

unable to get rows of "router.navigation"

While I am new to Aurelia, I am really liking Aurelia. I have read the "Getting started" with Aurelia and have done some reading on configure router. Specifically I am using http://mobilemancer.com/2016/11/18/using-router-in-aurelia-spa/ as a basis for my understanding.
I am following the normal conventions (app.ts, app.html and navigation.html). I am unable to get the "repeat.for" to iterate over the rows (specifically in this case one row) in router.navigation. I am sure that I am making some elementary mistake. But unable to find out what. Both of app.html and navigation.html seem to be "executing" in terms of seeing both "Aurelia Router Demo" and "nice" appearing on the browser.
Thanks for your help!
app.ts
import { autoinject } from 'aurelia-framework';
import { RouterConfiguration, Router } from 'aurelia-router';
#autoinject
export class App {
public router: Router;
configureRouter(config: RouterConfiguration, router: Router): void {
this.router = router;
config.title = ' Test ';
config.map( [
{route: ['', 'home'], name: 'home', moduleId: './home', nav: true, title: 'Home'},
]);
}
}
app.html
<template>
<require from="./navigation.html"> </require>
<h1> Aurelia Router Demo </h1>
<navigation router.bind="router"> </navigation>
<div class="page-host">
<router-view>
</router-view>
</div>
</template>
--navigation.html
<template bindable="router">
<h2> nice </h2>
<nav>
<ul>
<li repeat.for="row of router.navigation">
<a href.bind="row.href"> ${row.title} </a>
</li>
</ul>
</nav>
</template>
I don't see anything wrong at first glance. Try using compose instead of binding the router to see if that works. compose without a specified view-model attribute uses the consumer's view-model, removing the necessity of passing the router through data binding. Note that when requiring a custom element with a .html extension, Aurelia automatically generates a view model for it.
Basically, use compose instead of navigation:
app.html
<template>
<h1> Aurelia Router Demo </h1>
<compose view="./navigation.html"> </compose>
<div class="page-host">
<router-view>
</router-view>
</div>
</template>
And remove bindable="router" from:
navigation.html
<template>
<h2> nice </h2>
<nav>
<ul>
<li repeat.for="row of router.navigation">
<a href.bind="row.href"> ${row.title} </a>
</li>
</ul>
</nav>
</template>
In my configuration , including aurelia-routing.min.js as a script tag does NOT do the work. (Possible duplicate issue at How to install/setup Aurelia router/routes).
If I do a build ( au build) and include the vendor.js, then the thing (repeat.for) works.
Thanks for quick responses!