AngularJs create a drop-down sidebar menu

2

Hello community, I am trying to create a pull-down menu in a sideBar for the administrative part. The problem that results is that when I click on a sub-menu all the sub-menus are displayed and vice versa. Here is my code. I hope you can help me.

<div class="side-bar hide-on-med-and-down">
    <div class="row profile-side-bar">
        <div class="col s4">
            <img class="circle responsive-img" ng-src="assets/img/avatar.jpg">
        </div>
        <div class="col s8">
            <p>User Name</p>
            <small>Administrador</small>
        </div>
    </div>
    <ul>
        <li><a class="white-text" ng-click="openBar = !openBar"><i class="material-icons">menu</i> <span ng-class="{'text-open-side-bar': !openBar, 'text-close-side-bar': openBar}">MENÚ</span></a></li>
        <li><a href=""><i class="material-icons">dashboard</i><span ng-class="{'text-open-side-bar': !openBar, 'text-close-side-bar': openBar}">Texto menu</span></a></li>
        <li class="sub-nav">
            <a ng-click="childrenOpen = !childrenOpen"><i class="material-icons">dashboard</i><span ng-class="{'text-open-side-bar': !openBar, 'text-close-side-bar': openBar}">Texto menu</span><i class="nav-down material-icons">keyboard_arrow_down</i></a>
            <ul class="children" ng-class="{'show-children':childrenOpen,'hide-children':!childrenOpen}">
                <li><a href="#">Item #1</a></li>
                <li><a href="#">Item #2</a></li>
                <li><a href="#">Item #3</a></li>
                <li><a href="#">Item #4</a></li>
            </ul>
        </li>
        <li><a href=""><i class="material-icons">dashboard</i><span ng-class="{'text-open-side-bar': !openBar, 'text-close-side-bar': openBar}">Texto menu</span></a></li>
        <li class="sub-nav">
            <a ng-click="childrenOpen = !childrenOpen"><i class="material-icons">dashboard</i><span ng-class="{'text-open-side-bar': !openBar, 'text-close-side-bar': openBar}">Texto menu</span><i class="nav-down material-icons">keyboard_arrow_down</i></a>
            <ul class="children" ng-class="{'show-children':childrenOpen,'hide-children':!childrenOpen}">
                <li><a href="#">Item #1</a></li>
                <li><a href="#">Item #2</a></li>
                <li><a href="#">Item #3</a></li>
                <li><a href="#">Item #4</a></li>
            </ul>
        </li>
    </ul>
</div>

Clicking displays the two class="sub-nav" , this is logical because it changes the $ scope, but how could I only display the submenu where I clicked. Thanks for your kind answers

    
asked by Edgar Armand Herrera Pepe 11.07.2016 в 16:24
source

3 answers

3

Usually sidebars can be built from objects since adding and removing objects is much easier (except sometimes) than manipulating the entire html.

I leave you an example where I wrote two directives, one for the sidebar component and another for each menuitem that allows to keep the logic of each component separately. Now it's just a matter of adding and removing elements and customize to your liking.

angular.module('app', [])
  .directive('sidebar', function(menuitems) {
    return {
      restrict: 'EA',
      template: '<ul class="sidebar">' +
        '<li class="menuitem" ng-class="{active: item.active, subitems: !!item.subitems}" ng-repeat="item in menuitems" ng-click="toogleItem($index)">' +
        '<a ng-href="{{item.href}}" ng-if="item.href">{{item.title}}</a>' +
        '<menuitem item="item" ng-if="!item.href"></menuitem>' +
        '</li>' +
        '</ul>',
      link: function($scope) {
        $scope.menuitems = menuitems;

        $scope.toogleItem = function(index) {
          $scope.menuitems.forEach(function(item, idx) {
            item.active = index === idx && (!item.active || !item.subitems);

            if (!item.active && item.subitems) {
              item.subitems.forEach(function(si) {
                si.active = false;
              });
            }
          });
        }
      }
    };
  })
  .directive('menuitem', function() {
    return {
      restrict: 'EA',
      scope: {
        item: '='
      },
      template: '<span>{{item.title}}</span>' +
        '<ul ng-if="item.subitems" ng-show="item.active">' +
        '<li class="menuitem sub" ng-repeat="subitem in item.subitems" ng-class="{active: subitem.active}" ng-click="toogleSubItem($event, $index)">' +
        '<a ng-href="{{subitem.href}}" ng-if="subitem.href">{{subitem.title}}</a>' +
        '<span ng-if="!subitem.href">{{subitem.title}}</span>' +
        '</li>' +
        '</ul>',
      link: function($scope, $element) {
        $scope.toogleSubItem = function($event, index) {
          $event.stopPropagation();
          $scope.item.subitems.forEach(function(item, idx) {
            item.active = index === idx;
          });
        }
      }
    };
  })
  .factory('menuitems', function() {
    return [{
      title: 'Item1',
      href: '#item1'
    }, {
      title: 'Item2',
      subitems: [{
        title: 'Subitem 1',
        href: '#subitem 1'
      }, {
        title: 'Subitem 1',
        href: '#subitem 1'
      }]
    }, {
      title: 'Item3',
      href: '#item3'
    }];
  });
ul {
  list-style: none;
  padding: 0;
}
a,
a:visited,
a:hover {
  text-decoration: none;
  color: white;
}
ul.sidebar {
  position: fixed;
  top: 0;
  left: 0;
  height: 100%;
  width: 200px;
  background-color: #322f30;
  color: white;
}
.menuitem {
  position: relative;
  padding: 10px;
  margin: 0;
  cursor: pointer;
}
.menuitem.active {
  background-color: #238db0;
}
.menuitem.active:hover {
  background-color: #238db0;
}
.menuitem:hover {
  background-color: royalblue;
}
.menuitem.subitems:after {
  content: '˅';
  position: absolute;
  right: 0;
  top: 0;
  width: 15px;
  bottom: 0;
  padding-top: 10px;
}
.menuitem.sub.active {
  background-color: #0c3441;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
  <sidebar></sidebar>
</div>
    
answered by 12.07.2016 в 16:01
0

You would need two attributes within your model, one for each menu, in order to modify the correct one. Something like this:

<ul>
    <li><a class="white-text" ng-click="openBar1 = !openBar1"><i class="material-icons">menu</i> <span ng-class="{'text-open-side-bar': !openBar1, 'text-close-side-bar': openBar1}">MENÚ</span></a></li>
    <li><a href=""><i class="material-icons">dashboard</i><span ng-class="{'text-open-side-bar': !openBar1, 'text-close-side-bar': openBar1}">Texto menu</span></a></li>
    <li class="sub-nav">
        <a ng-click="childrenOpen1 = !childrenOpen1"><i class="material-icons">dashboard</i><span ng-class="{'text-open-side-bar': !openBar1, 'text-close-side-bar': openBar1}">Texto menu</span><i class="nav-down material-icons">keyboard_arrow_down</i></a>
        <ul class="children" ng-class="{'show-children':childrenOpen1,'hide-children':!childrenOpen1}">
          ..
        </ul>
    </li>
    <li><a href=""><i class="material-icons">dashboard</i><span ng-class="{'text-open-side-bar': !openBar2, 'text-close-side-bar': openBar2}">Texto menu</span></a></li>
    <li class="sub-nav">
        <a ng-click="childrenOpen2 = !childrenOpen2"><i class="material-icons">dashboard</i><span ng-class="{'text-open-side-bar': !openBar2, 'text-close-side-bar': openBar2}">Texto menu</span><i class="nav-down material-icons">keyboard_arrow_down</i></a>
        <ul class="children" ng-class="{'show-children':childrenOpen2,'hide-children':!childrenOpen2}">
          ...
        </ul>
    </li>
</ul>

Thus each attribute is related to the correcot element

    
answered by 11.07.2016 в 16:30
0

Angular (and the frameworks / libraries in general) is designed so that during the development you need to rewrite the least amount of code, and you focus better on the planning and implementation of the business logic. This is why Angular gives us the option to use components , which are nothing more than code abstractions that you can reuse one and again. The important thing of these, is that they maintain an internal scope, which is not shared with the rest of the controllers, so you can have 10 menus and each one would have its own internal state (open / closed, for example).

Here is a simple example:

var menuController = function(){
  var ctrl = this;
  
  ctrl.opened = false;

  ctrl.toggle = function(){
    ctrl.opened = !ctrl.opened;
  };
};


angular
.module('directiveApp', [])
.controller('controllerApp', ['$scope', function($scope) {
  $scope.menus = [
    {name: 'menu 1'},
    {name: 'menu 2'},
    {name: 'menu 3'},
    {name: 'menu 4'},    
  ];
}])
.component('menuComponent', {
  template: "<p class='menu' ng-click='$ctrl.toggle()'>{{$ctrl.name}}</p><ul ng-show='$ctrl.opened'><li>submenu</li></ul>",
  controller: menuController,
  bindings: {
    name: '<'
  }
});
  
.menu {
  display:inline-block;
  padding:10px;
  border:1px solid red;
  overflow:hidden;
  width:100%;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.7/angular.min.js"></script>


<div ng-app="directiveApp">
  <div ng-controller="controllerApp">
    <p>Menus</p>
    
    <menu-component ng-repeat="menu in menus" name="menu.name" />

  </div>
</div>

This is a response to the user's general problem and not the code written by him

    
answered by 12.07.2016 в 16:49