I'm doing this simple hamburger menu in pure HTML and CSS (from CodeMyUI), and I would like it to auto close when a link is clicked. Using vue with router-links the page isn't reloaded.
<template>
<nav>
<input type="checkbox" id="menu" />
<label for="menu"></label>
<div class="menu-content">
<router-link to="/" class="mainlink">{{ $t("message.navFront") }}</router-link>
<router-link to="/about" class="mainlink">{{ $t("message.navAbout") }}</router-link>
<router-link to="/portfolio" class="mainlink">{{ $t("message.navPort") }}</router-link>
</div>
</nav>
</template>
Have you tried using v-model to bind the value?
<input type="checkbox" id="menu" v-model="checkboxValue />
<router-link to="/" class="mainlink" #click.native="uncheck">{{ $t("message.navFront") }}</router-link>
Then you should at the checkboxValue to the data() and the uncheck method to methods.
data() {
return {
checkboxValue: false,
}; },
methods:{
uncheck(){
this.checkboxValue = false;
}}
With this you should be able to deselect the checkbox once you click on the links.
Edit: So apparently the routes-link element doesn't support #click, but it might support #click.native (or v-on:click.native)
you need to uncheck <input type="checkbox" id="menu" /> when a click happens on a link within the menu.
with a nice vue event explained like here: https://v2.vuejs.org/v2/guide/events.html#Method-Event-Handlers
maybe somehow like this:
template
<router-link to="/" class="mainlink" v-on:click="uncheck"> {{ $t("message.navFront") }} </router-link>
js
methods: {
uncheck: function (event) {
document.getElementById("menu").checked = false;
}
}
Related
function Navbar() {
const [shownavcontents, setShownavcontents] = useState(false)
if(shownavcontents){
document.getElementsByClassName("navbardivofmobiledevice").style.display = "none";
}else{
document.getElementsByClassName("navbardivofmobiledevice").style.display = "block";
}
return (
<>
<div className="top">
<Searchbar />
<AiOutlineMenu size={20} className="outlinemenu" onClick={() => {setShownavcontents(true)}} />
</div>
<div className="navbardivofmobiledevice">
<ul>
<li>
Home
</li>
<li>
Members
</li>
<li>
All Posts
</li>
<li>
My Posts
</li>
</ul>
</div>
</>
);
}
As you see I am trying to make responsive navbar, in this case, for mobile devices. I've faced one problem. I've made button on top of navbar and some navbar contents which I want to display only whenever user will click this button and vice versa. So I tried using hooks to check if the user clicked the button which works perfectly, only thing that doesn't works is this if else statements it seems like document.getElementsByClassName("navbardivofmobiledevice").style.display = "none"; doesn't have an effect here. So my question is what is the alternative of this? What can I do here?
This is imperative code:
document.getElementsByClassName("navbardivofmobiledevice").style.display = "none";
With React, you rarely get references to DOM elements and update them manually, and in any case, you do it using Refs, not with the getElement... or querySelector... methods). Instead, you write declarative code and let React take care of the DOM updates for you.
In this case, simply add or remove a hidden attribute or CSS class that has display: none from your JSX:
function Navbar() {
const [shownavcontents, setShownavcontents] = useState(false);
return (
<>
<div className="top">
<Searchbar />
<AiOutlineMenu size={20} className="outlinemenu" onClick={() => {setShownavcontents(true)}} />
</div>
<div className="navbardivofmobiledevice" hidden={ !shownavcontents }>
<ul>
<li>
Home
</li>
<li>
Members
</li>
<li>
All Posts
</li>
<li>
My Posts
</li>
</ul>
</div>
</>
);
}
If you prefer to use a class, assuming you have defined a CSS class .isHidden { display: none; } you would use this line instead:
<div className={ `navbardivofmobiledevice${ shownavcontents ? '' : ' isHidden' }` }>
Regarding what some comments are mentioning about rendering that conditionally like so:
function Navbar() {
const [shownavcontents, setShownavcontents] = useState(false);
return (
<>
<div className="top">
<Searchbar />
<AiOutlineMenu size={20} className="outlinemenu" onClick={() => {setShownavcontents(true)}} />
</div>
{ shownavcontents && (
<div className="navbardivofmobiledevice">
<ul>
<li>
Home
</li>
<li>
Members
</li>
<li>
All Posts
</li>
<li>
My Posts
</li>
</ul>
</div>
) }
</>
);
}
I would avoid that, as hiding your main navigation from Google and other search engines will harm your SEO. You need to hide it visually but still have it in the DOM.
If you want to do better than that, add all the appropriate ARIA attributes and logic for a navigation menu with nested submenus, as explained here:
https://www.w3.org/WAI/ARIA/apg/example-index/menubar/menubar-navigation
I am disabling document body scroll when my mobile nav is open which is working as expected. However it was not removing the overflow hidden when user would click a link to another route/page. I created a simple method to remove the overflow hidden when a link is clicked in the nav when the menu is open, which does work with one small caveat. When the user is on say page "home" and the mobile nav is OPEN, when they click the link "home" inside of the mobile nav it is closing the menu, and I understand I have done that with the method I created. Is there a way to prevent that event from firing when clicking the link of the page you are on?
<header :class="{ 'header-active': activeHamburger }">
<nav class="nav-row nav-row--primary" aria-label="Main Navigation">
<ul class="row--flex justify--space-between">
<li>
<router-link to="/" #click="removeOverflowHidden();
">
home
</router-link>
</li>
<li>
<router-link to="About" #click="removeOverflowHidden();
">
about
</router-link>
</li>
<li>
<router-link to="Work" #click="removeOverflowHidden();
">
work
</router-link>
</li>
<li>
<router-link to="Contact" #click="removeOverflowHidden();
">
contact
</router-link>
</li>
</ul>
</nav>
</header>
data() {
return {
activeHamburger: false
};
},
watch: {
activeHamburger: function() {
if (this.activeHamburger) {
document.documentElement.style.overflow = "hidden";
return;
}
document.documentElement.style.overflow = "auto";
}
},
methods:{
removeOverflowHidden() {
this.activeHamburger = false;
}
}
You can pass the route value to the method and check that it's not same as the current route before executing.
<template>
<header :class="{ 'header-active': activeHamburger }">
<nav class="nav-row nav-row--primary" aria-label="Main Navigation">
<ul class="row--flex justify--space-between">
<li>
<router-link to="/" #click="removeOverflowHidden('home');
">
home
</router-link>
</li>
<li>
<router-link to="About" #click="removeOverflowHidden('about');
">
about
</router-link>
</li>
<li>
<router-link to="Work" #click="removeOverflowHidden('work');
">
work
</router-link>
</li>
<li>
<router-link to="Contact" #click="removeOverflowHidden('contact');
">
contact
</router-link>
</li>
</ul>
</nav>
</header>
</template>
<script>
export default {
data() {
return {
activeHamburger: false
};
},
watch: {
activeHamburger: function() {
if (this.activeHamburger) {
document.documentElement.style.overflow = "hidden";
return;
}
document.documentElement.style.overflow = "auto";
}
},
methods:{
removeOverflowHidden(value) {
if (this.$route.path !== value) {
this.activeHamburger = false;
}
}
}
}
</script>
I haven't seen your routes but you can also use this.$route.name if you prefer and adjust accordingly the values you pass to the method.
I've been working on a sub menu system for vue.js that gets populated by the current route's children. I recently posted a question about it and it was answered here.
Now I'm trying to improve upon that but I'm having trouble finding out how to get a component's path or namespace (not sure what word to use). Currently I see what I want in the Vue Dev tools I just don't now how to get those properties.
I've tried {{$route.path}} but that only gives me the full path.
Another thing I've tried which kind of helps is storing the current path the first time I load the menu. Which preserves the path I want to be appending to. The only issue is when i navigate directly to the page it loads the menu with the pages url which then breaks the functionality.
Here is the code:
<template>
<nav id="sidebar">
<div class="sidebar-header">
<h3>Bootstrap Sidebar</h3>
</div>
<h2>Route: {{ }}</h2>
<ul class="list-unstyled components" v-for="(route, index) in $router.options.routes.filter(x=>x.path==path)">
<li v-for="child in route.children">
<a class="nav-item" :key="index">
<router-link :to="{path: path+'/'+child.path}" exact-active-class="active">
<icon :icon="route.icon" class="mr-2" /><span>{{ child.path }}</span>
</router-link>
</a>
</li>
</ul>
</nav>
</template>
<script>
export default {
data() {
return {
path: this.$route.path
}
},
methods: {
},
}
</script>
I really want something closer to this though, where instead of using $route.path to return the full path like /traveler/Create I want something to just return /traveler or whatever the path for it's router-view is:
<template>
<nav id="sidebar">
<div class="sidebar-header">
<h3>Bootstrap Sidebar</h3>
</div>
<ul class="list-unstyled components" v-for="(route, index) in $router.options.routes.filter(x=>x.path==$route.path)">
<li v-for="child in route.children">
<a class="nav-item" :key="index">
<router-link :to="{path: $route.path+'/'+child.path, params: { idk: 1 }}" exact-active-class="active">
<icon :icon="route.icon" class="mr-2" /><span>{{ child.path }}</span>
</router-link>
</a>
</li>
</ul>
</nav>
</template>
<script>
import { travelerroutes } from '../../router/travelerroutes'
export default {
data() {
console.log(travelerroutes);
return {
travelerroutes,
collapsed: true
}
},
methods: {
toggleCollapsed: function (event) {
this.collapsed = !this.collapsed
}
}
}
</script>
To get the path of the current component I had to just use the $Route.matched property. In my case because I didn't want to include the childrens' paths I used the first match like this $Route.matched[0].path
you can learn more about it here
I also used it to update my other question/answer
Essentially you can use it in a template like this:
<template>
<nav id="sidebar">
<div class="sidebar-header">
<h3>Bootstrap Sidebar</h3>
</div>
<ul class="list-unstyled components" v-for="(route, index) in $router.options.routes.filter(x=>x.path==$route.matched[0].path)">
<li v-for="child in route.children">
<a class="nav-item" :key="index">
<router-link :to="route.path+'/'+child.path" exact-active-class="active">
<icon :icon="route.icon" class="mr-2" /><span>{{ child.name }}</span>
</router-link>
</a>
</li>
</ul>
</nav>
</template>
<script>
export default {
data() {
return {
}
}
}
</script>
I am trying to build a navigation which staggers the appearance of the contained list items when the menu/navigation is shown.
I have a burger symbol, and when clicked it renders the navigation (fullscreen).
I would now like to have an animation, where the different list items (actual links) appear with some delay to each other, the top one being the first and the bottom one the last.
I thought I could do this with vue's <transition-group> and list transitions, but all the examples are about adding and removing list items, whereas I have all of them from the beginning.
I then read about this: Vue.js: Staggering List Transitions
And I thought that might be it.
Unfortunately I could not get it to work either.
Do you have any hints how I could do that?
So far, I render the navigation with v-if:
<transition name="u-anim-fade" mode="in-out">
<Navigation v-if="navMenuOpen" />
</transition>
Within the Navigation component:
<nav>
<ul class="Navigation__list">
<li
v-for="(item, key) in menu.items"
class="Navigation__item"
:key="`nav-item-${key}`">
<nuxt-link>
<span v-html="item.title" />
</nuxt-link>
</ul>
</nav>
(I left out some stuff simplify the code here)
When I add the enter/leave/onEnter functions which are suggested here: Vue.js: Staggering List Transitions
v-bind:css="false"
v-on:before-enter="beforeEnter"
v-on:enter="enter"
v-on:leave="leave"
Like so:
<nav>
<transition-group
name="staggered-fade"
tag="ul"
class="Navigation__list"
v-bind:css="false"
v-on:before-enter="beforeEnter"
v-on:enter="enter"
v-on:leave="leave"
>
<li
v-for="(item, key) in menu.items"
class="Navigation__item"
:key="`nav-item-${key}`">
<nuxt-link>
<span v-html="item.title" />
</nuxt-link>
</transition-group>
</nav>
The methods (which I add to the methods of course) would not even be executed when I render the Navigation component. Probably because the items are not added or removed.
Probably because the items are not added or removed.
You're right, and what you need is this:
Transitions on Initial Render
If you also want to apply a transition on the initial render of a
node, you can add the appear attribute:
<transition appear>
<!-- ... -->
</transition>
I just tried it and if not present, the listener functions aren't called on the initial render.
Note that it works as well with <transition-group> components.
For vue 3 user, with GSAP
<template>
<div>
<transition-group
appear
tag="ul"
#before-enter="beforeEnter"
#enter="enter"
>
<li v-for="(item, index) in items" :key="icon.name" :data-index="index">
<div>{{ item.text }}</div>
</li>
</transition-group>
</div>
</template>
<script>
import { ref } from 'vue'
import { gsap } from 'gsap'
export default {
setup() {
const items = ref([
{ text: 'by email' },
{ text: 'by phone' },
])
const beforeEnter = (el) => {
el.style.opacity = 0
el.style.transform = 'translateY(100px)'
}
const enter = (el, done) => {
gsap.to(el, {
opacity: 1,
y: 0,
duration: 0.8,
onComplete: done,
delay: el.dataset.index * 0.2
})
}
return { items, beforeEnter, enter }
}
}
</script>
More info here
This is my first Vue.js component, I try to emit event from it. But my browser doesnt see 'updateValue' method... Whats wrong?
<template>
<div>
<ul class="nav flex-column">
<li v-for="item in items">
<div class="nav-link" onclick="updateValue(1)">
{{ item.name }}
</div>
</li>
</ul>
</div>
</template>
<script>
export default {
props: ['items'],
methods: {
updateValue: function (value){
this.$emit('change', value);
}
}
}
</script>
In Vue you should use #click or v-on:click instead of onclick.