I have a bootstrap 3 menu which I am trying to use Angular JS on.
I have the basic menu working, and the ng-class correctly works, applying it only in the menu has children.
What I am trying to do now is to only have the menu "work" (display the second nested UL) only if the parent LI has child data. So, I have this:
<ul class="nav navbar-nav">
<li ng-class="{'dropdown':(n.children.length)}" ng-repeat="n in navData">
<a data-target="#" ng-attr="{'data-toggle=dropdown':(n.children.length>0)}">{{n.label}}</a>
<ul class="dropdown-menu" role="menu">
<li ng-repeat="p in n.children">{{p.label}}</li>
</ul>
</li>
</ul>
The part I have wrong, or that does not work is the ng-attr on the A tag. The data-toggle=dropdown is what causes the menu to work. Currently, none of the menus work even if they have children.
My model is
var nav = [{
label: 'Pages',
value: 'pages',
children: [{
label: 'Home',
value: 'home'
}, {
label: 'Left Nav',
value: 'left-nav'
}]
}, {
label: 'Components',
value: 'components'
}];
So, "Pages" has children and "Components" does not. The ng-class works as expected.
EDIT: I have added a "toggle" value to the model, set to "dropdown" or "" and then this works:
<ul class="nav navbar-nav">
<li ng-class="{'dropdown':(n.children.length)}" ng-repeat="n in navData">
<a data-target="#" data-toggle="{{n.toggle}}">{{n.label}}</a>
<ul class="dropdown-menu" role="menu">
<li ng-repeat="p in n.children">{{p.label}}</li>
</ul>
</li>
</ul>
I have added a "toggle" value to the model, set to "dropdown" or "" and then this works:
<ul class="nav navbar-nav">
<li ng-class="{'dropdown':(n.children.length)}" ng-repeat="n in navData">
<a data-target="#" data-toggle="{{n.toggle}}">{{n.label}}</a>
<ul class="dropdown-menu" role="menu">
<li ng-repeat="p in n.children">{{p.label}}</li>
</ul>
</li>
</ul>
Related
I have a mobile navigation with two dropdown menus. Here is the markup of the nav:
<div id="mobile-menu" class="mobile-menu container fixed">
<ul>
<li>Home</li>
<li class="dropdown">
Articles <i class="bi bi-chevron-down"></i>
<ul class="submenu hidden">
<li>Submenu item 1</li>
<li>Submenu item 2</li>
</ul>
</li>
<li>Contact</li>
<li class="dropdown">
My account <i class="bi bi-chevron-down"></i>
<ul class="submenu hidden">
<li>Dashboard</li>
<li>Profile</li>
</ul>
</li>
</ul>
</div>
The dropdown menus should open/expand when clicked. Originally I grabbed the dropdown and submnenu classes like this:
const mobileDropdown = document.querySelector(".dropdown");
const mobileSubMenu = document.querySelector('.submenu');
and used an eventlistener to toggle the "hidden" class which is just a display:none
mobileDropdown.addEventListener('click', () => {
mobileSubMenu.classList.toggle('hidden');
});
The problem with this is that this will only open the first dropdown menu and I cannot open the second.
When I try to use querySelectorAll instead of just querySelector then i get thiserror:
Uncaught typeError addEventListener is not a function
Here I read that with querySelectorAll I need to use a for or foreach loop.
but i think I'm messing it up. I tried this:
const mobileDropdown = document.querySelectorAll(".dropdown");
const mobileSubMenu = document.querySelector('.submenu');
mobileDropdown.forEach(md => md.addEventListener('click', () => {
mobileSubMenu.classList.toggle('hidden');
}));
Now I don't get an error, I can open the first dropdown menu, but when I try to open the second dropdown menu, the first one opens. What am I doing wrong? Thanks in advance.
The problem in your code is that each mobileDropdown's clickEvent was linked to the first submenu, you should link mobileDropdown's clickEvents to their submenu children like that
document.querySelectorAll(".dropdown").forEach(md => md.addEventListener('click', () => {
md.querySelector(".submenu").classList.toggle('hidden');
}));
.hidden {
visibility: hidden;
}
<div class="mobile-menu container fixed" id="mobile-menu">
<ul>
<li>Home</li>
<li class="dropdown">
Articles <i class="bi bi-chevron-down"></i>
<ul class="submenu hidden">
<li>Submenu item 1</li>
<li>Submenu item 2</li>
</ul>
</li>
<li>Contact</li>
<li class="dropdown">
My account <i class="bi bi-chevron-down"></i>
<ul class="submenu hidden">
<li>Dashboard</li>
<li>Profile</li>
</ul>
</li>
</ul>
</div>
here is a solution. You have to listen to the 'click' event for "submenu" on the element of "dropdown".
const mobileDropdown = document.querySelectorAll(".dropdown");
mobileDropdown.forEach((md) => {
md.addEventListener("click", () => {
const mobileSubMenu = md.querySelector(".submenu");
mobileSubMenu.classList.toggle("hidden");
});
});
mobileSubMenu always the first!
In a Vue-component, I have a menu like this:
<ul class="menu-outer-wrapper">
<li>Foo 1</li>
<li class="has-children">
Foo 2
<ul>
<li>Child 1</li>
<li>Child 2</li>
<li>Child 3</li>
</ul>
</li>
<li>Foo 5</li>
<li class="has-children">
Foo 6
<ul>
<li>Child 1</li>
<li>Child 2</li>
</ul>
</li>
<li>Foo 7</li>
<li>Foo 8</li>
</ul>
And I would like to add the class hovered to the li.has-children-elements upon hover (mouseenter) (to be able to make some nicer animations for the children of that dropdown. And remove that class on mouseleave.
I know that there are options to do this with pure CSS, - but controlling delays and soft fade-in's are a pain (and become very messy very fast, without adding some classes).
I imagined doing something like this:
...
mounted(){
let liWithChildren = document.querySelectorAll( '.menu-outer-wrapper > li.has-children' );
liWithChildren.forEach( (event, window) => {
// Somehow add the class to the hovered element here.
// But I don't know how. Or if it's a good/bad idea (performance-wise).
}
}
But is that the way to go? And can I do it without using data (since the menu is dynamically generated by a CMS-system.
Update 1
I'm trying to keep my markdown readable. So I would like to avoid something like this:
<ul class="menu-outer-wrapper">
<li :class="[ { 'hovered' : someVar } ]">
Foo 1
</li>
<li :class="[ { 'hovered' : someVar }, 'has-children' ]">
Foo 2
<ul>
<li>Child 1</li>
<li>Child 2</li>
<li>Child 3</li>
</ul>
</li>
<li :class="[ { 'hovered' : someVar } ]">
Foo 2
</li>
...
...
...
Both since it won't gel with the dynamically generated menu.
And also since it add a lot of noise to the markdown.
Update 2
I simplified the example, to make it easier to digest. But due to the comments I figured I would elaborate on the dynamic generated menu. I'm making it something like this:
<nav id="secondary-menu" v-if="secondaryMenu">
<ul>
<li
:class="[ { 'has-children': r.children } ]"
v-for="(r, r_key, r_index) in secondaryMenu">
<a :href="r.url" :title="r.title">
{{ r.title }}
</a>
<ul class="children" v-if="r.children">
<li v-for="(c1, c1_key, c1_index) in r.children">
<a :href="c1.url" :title="c1.title">
{{ c1.title }}
</a>
</li>
</ul>
</li>
</ul>
</nav>
You just need the #mouseenter and #mouseleave events. All you need to do is listen for the appropriate events on all list-items that could have children, then perform your class addition (or removal) if the target element has the class of "has-children". Here's how I would do it:
<template>
<nav id="secondary-menu" v-if="secondaryMenu">
<ul>
<li
:class="[{ 'has-children': r.children }]"
v-for="(r, r_key, r_index) in secondaryMenu"
:key="`${r_key}-${r_index}`"
#mouseenter="addClass"
#mouseleave="removeClass"
>
<a :href="r.url" :title="r.title">
{{ r.title }}
</a>
<ul class="children" v-if="r.children">
<li
v-for="(c1, c1_key, c1_index) in r.children"
:key="`${c1_key}-${c1_index}`"
>
<a :href="c1.url" :title="c1.title">
{{ c1.title }}
</a>
</li>
</ul>
</li>
</ul>
</nav>
</template>
<script>
export default {
name: "HoverNav",
props: {
secondaryMenu: {
type: Array,
required: true,
},
},
methods: {
addClass: function (e) {
if (e.target.classList.contains("has-children")) {
e.target.classList.add("hovered");
}
},
removeClass: function (e) {
if (e.target.classList.contains("has-children")) {
e.target.classList.remove("hovered");
}
},
},
};
</script>
Here's a very unaesthetic sandbox of this in action. Let me know if this works for you :)
https://codesandbox.io/s/headless-brook-ysq97?file=/src/components/HoverNav.vue:0-1169
I have a kind of side bar menu. Like this:
Projects:
All
project1
project2
When I click an item I want to changed it the background-color. (from black to green).
Projects:
All
project1 // This was clicked and I want to be GREEN
project2
But, what I did until now was to changed the color for all of the projects when I clicked a project. All of them are green know. I don't know how to do that for a particular item.
<div class="container">
<h5>Projects: </h5>
<div class="container-fluid">
<ul class="nav navbar-nav side-nav">
<li routerLinkActive="active" class="nav-item">
<a (click)="activeProject()" [ngStyle]="{'background-color':isActiveProject? 'green' : 'white' }" routerLink="/tasks" href="#">All</a>
</li>
</ul>
<ul class="nav navbar-nav side-nav" *ngFor="let project of projects">
<li routerLinkActive="active" class="nav-item" >
<a (click)="activeProject()" [ngStyle]="{'background-color':isActiveProject? 'green' : 'white' }" [routerLink]="['/tasks/project/', project.projectId]" href="#">{{project.projectName}}</a>
</li>
</ul>
</div>
</div>
In the component:
isActiveProject: boolean;
activeProject() {
this.isActiveProject = true;
}
I suppose that this active project method is apply for all the li elements, an remains active when it was stetted on true.
I see you have a routerLinkActive="active", this should set the active class on the li element. Then in your CSS, you can do:
li.nav-item.active { background: green; }
You may have to set [routerLinkActiveOptions]="{exact: true}" for exact routing and highlighting.
Then you can get rid of isActiveProject for the change of background color and the ngStyle.
You need to store the index of the active project and compare it to the item in the ngFor loop:
Component:
public activeProjectIndex: number;
public activeProject(index: number): void {
this.activeProjectIndex = index;
}
HTML:
<ul class="nav navbar-nav side-nav" *ngFor="let project of projects; let i = index">
<li routerLinkActive="active" class="nav-item" >
<a (click)="activeProject(i)" [ngStyle]="{'background-color': activeProjectIndex === i ? 'green' : 'white' }" [routerLink]="['/tasks/project/', project.projectId]" href="#">{{project.projectName}}</a>
</li>
</ul>
You can track the clicked item properties ($event.target or whatever you need) by adding the "$event" property in your click function:
<a (click)="activeProject($event)"
You also have access to routerLinkActive decorator, so you can also hook your current menu item by its parent class
Please try this, this will initially set green background for 'All' and change the background of the tab when you clicked on it
HTML
<div class="container">
<h5>Projects: </h5>
<div class="container-fluid">
<ul class="nav navbar-nav side-nav">
<li routerLinkActive="active" class="nav-item">
<a (click)="activeProject = 'all'" [ngStyle]="{'background-color':activeProject == 'all' ? 'green' : 'white' }" routerLink="/tasks" href="#">All</a>
</li>
</ul>
<ul class="nav navbar-nav side-nav" *ngFor="let project of projects">
<li routerLinkActive="active" class="nav-item" >
<a (click)="activeProject = project.projectId" [ngStyle]="{'background-color':activeProject == project.projectId? 'green' : 'white' }" [routerLink]="['/tasks/project/', project.projectId]" href="#">{{project.projectName}}</a>
</li>
</ul>
Component
activeProject='all'
I have a menu which I have working fine but I need a small change and that is I need the parent menu to toggle the submenu, currently if you click the parent menu the submenu appears but I need it so when you click the parent menu again it closes the sub menu.
You can see the menu in action here.
and this is the javascript that is for the menu:
$('.dropdown-toggle').click(function() {
$('.dropdown').css('display', 'none'); // Hide submenus when other submenus are clicked
$(this).next('.dropdown').toggle();
});
$(document).click(function(e) {
var target = e.target;
if (!$(target).is('.dropdown-toggle') && !$(target).parents().is('.dropdown-toggle')) {
$('.dropdown').hide();
}
});
This is the menu html
<nav class="main">
<a class="dropdown-toggle" href="#" title="Menu">Menu One</a>
<ul class="dropdown">
<li>Menu Item</li>
<li>Menu</li>
<li>Settings</li>
<li>Search</li>
</ul>
</nav>
Replace this code:
$('.dropdown-toggle').click(function() {
$('.dropdown').css('display', 'none'); // Hide submenus when other submenus are clicked
$(this).next('.dropdown').toggle();
});
With the code below:
$('.dropdown-toggle').click(function() {
var $currentDropdown = $(this).next('.dropdown');
$currentDropdown.siblings('.dropdown').not($currentDropdown).removeClass('toggled');
$currentDropdown.siblings('.dropdown').not($currentDropdown).hide();
$currentDropdown.toggleClass('toggled');
$currentDropdown.toggle();
});
That should do it.
I think this is the simplier way :)
Hope you helped
$(".dropdown").css('display', 'none');
$('.dropdown-toggle').click(function(e) {
if ($(this).next().is(":visible")){
$(this).next().hide();
}else{
$(".dropdown").hide();
$(this).next().show();
}
});
a {
display: block;
}
<script src="https://code.jquery.com/jquery-3.1.0.js"></script><nav class="main">
<nav>
<a class="dropdown-toggle" href="#" title="Menu">Menu One</a>
<ul class="dropdown">
<li>Menu Item</li>
<li>Menu</li>
<li>Settings</li>
<li>Search</li>
</ul>
<a class="dropdown-toggle" href="#" title="Menu">Menu One</a>
<ul class="dropdown">
<li>Menu Item</li>
<li>Menu</li>
<li>Settings</li>
<li>Search</li>
</ul>
</nav>
I think this code will be enough.
$('.dropdown-toggle').click(function() {
$(this).next('.dropdown').toggle();
});
Codepen Example
This can be done easily with Bootstrap:
<li class="submenu">
<i class="la la-user"></i>
<span> Main menue </span>
<span class="menu-arrow"></span>
<ul style="display: none;">
<li>sub menu 1</li>
<li> sub menu 2 </li>
<li> sub menu 3 </li>
</ul>
</li>
Currently, I am repeating code for my navigation menu on every single tab, but I want to use partials so there's no duplicate code.
This is what I am using (below), with the active class on a different list element depending on the file. Instead, I'd like to use a partial {{> fruits-nav}}, but I can't find any information on how I would set the active class depending on which file is including the partial.
<div id="table-nav-tabs">
<ul class="nav nav-tabs">
<li class="apple">apple</li>
<li class="active orange">orange</li>
<li class="mango">mango</li>
<li class="pineapple">pineapple</li>
</ul>
</div>
I think you can even make it slightly simpler and more readable:
<div id="table-nav-tabs">
<ul class="nav nav-tabs">
<li class="{{#if active.apple}}active{{/if}} apple">apple</li>
<li class="{{#if active.orange}}active{{/if}} orange">orange</li>
<li class="{{#if active.mango}}active{{/if}} mango">mango</li>
<li class="{{#if active.pineapple}}active{{/if}} pineapple">pineapple</li>
</ul>
</div>
and while rendering:
active: { pineapple: true }
You can pass data to a partial so you can do this in your partial:
<div id="table-nav-tabs">
<ul class="nav nav-tabs">
<li class="{{#if active_apple}}active{{/if}} apple">apple</li>
<li class="{{#if active_orange}}active{{/if}} orange">orange</li>
<li class="{{#if active_mango}}active{{/if}} mango">mango</li>
<li class="{{#if active_pineapple}}active{{/if}} pineapple">pineapple</li>
</ul>
</div>
and then pull it in like this:
{{> fruits-nav active}}
and just make sure active has he appropriate flag set for the current fruit.
Demo: http://jsfiddle.net/ambiguous/GesND/