I am creating a sidebar based off of this one
http://plnkr.co/edit/xzcjStdvmkI2rpfMzLjI?p=preview found from this tutorial
However I want to keep the selection active once the link is clicked. I've tried adding data-toggle, but that seems to work for nav-pills instead of navbar. I also found an is-active-nav directive that I've attempted to implement:
angular.module('sidebarmodule')
.directive('isActiveNav', ['$location', function ($location) {
return {
restrict: 'A',
link: function (scope, element) {
scope.location = $location;
scope.$watch('location.path()', function (currentPath) {
if ('/#' + currentPath === element[0].attributes['href'].nodeValue) {
element.parent().addClass('active');
} else {
element.parent().removeClass('active');
}
});
}
};
}]);
That I call in the sidebar template like so:
<li> <a is-active-nav href="#">Link1</a> </li>
Another approach I tried is in my controller adding $scope.isActive = function (viewLocation) { return viewLocation === $location.path(); }; and then adding to the template: <li ng-class="{ active: isActive('/importinvoice')}">First Link</li>
However, nothing I do will keep the sidebar elements highlighted after navigating to that page. I am trying to keep out of jQuery as I want it to be an AngularJS solution only.
I tried most of the solutions from this stackoverflow answer but can't get any of them to work on the plunker.
You can create a tab property in $scope, assign it a tab identifier and then use ng-class="{'active': tab==='tab1'}"
use this :
<div class="sidebar" sidebar-directive="state" ng-init="item=-1">
Navigation
<ul class="navigation">
<li class="navigation-items"> Link1
</li>
<li class="navigation-items"> Link2
</li>
<li class="navigation-items"> Link3
</li>
<li class="navigation-items"> Link4
</li>
<li class="navigation-items"> Link4
</li>
</ul>
</div>
see link : http://plnkr.co/edit/xzcjStdvmkI2rpfMzLjI?p=preview
Related
I have a directive placed in my navigation header. The point of it is to add/remove the active class. It does this by hooking into $routeChangeSuccess and looking at the anchors where the href is the route name.
angular.module('frApp')
.directive('bsActiveLink', ['$location', function ($location) {
return {
restrict: 'A', //use as attribute
replace: false,
link: function (scope, elem) {
//after the route has changed
scope.$on("$routeChangeSuccess", function () {
console.log("Route changed");
var hrefs = ['/#' + $location.path(),
'#' + $location.path(), //html5: false
$location.path()]; //html5: true
angular.forEach(elem.find('a'), function (a) {
a = angular.element(a);
if (-1 !== hrefs.indexOf(a.attr('href'))) {
a.parent().addClass('active');
} else {
a.parent().removeClass('active');
}
});
});
}
};
}]);
div class="col-3" style="vertical-align: middle;" bs-active-link>
<ul class="nav navbar-nav">
<li class="nav-item active">
<a class="nav-link" href="/unpaid">Unpaid</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/paid">Paid</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/recon">Recon</a>
</li>
</ul>
</div>
The main issue is this event doesn't get called on initial load. Now normally that's not an issue as you can see I set one as default and I have the angularjs routing redirect to this. But if I place another route directly into the address bar it'll load that one but visually the default '/unpaid' route has the active class on it. If I remove the default active class in the html then nothing is active. I'm seeing that my $routeChangeSuccess isn't hooked yet when the page loads.
It would seem to me that directives couldn't rely on this event hook on startup? What's the workaround to that?
Have you tried something like:
link: function (scope, elem) {
function update(...) {}
// just call here
update();
scope.$on("$routeChangeSuccess", update);
P.S. Such directives do not follow angular ideas. As an example why it is bad - if at some point new element appears in DOM - it wont be covered by directive. E.g. look at ui-router and u will see how this should be done:
~something like this~~
<div>
<a href="/unpaid" my-active-if="unpaid">... // <- custom directive on each href, sp no weird find required
<a href="/paid" my-active-if="paid">...
In angularjs to show/hide dynamic submenu, I am adding and removing dynamic classes in js file. Every time when the state changes in url (i.e ui-sref={{mymenu.url}}) sub menu is not visible. If there is no state change sub menu is working properly. Can anyone please suggest.
Html
<li ng-repeat='mymenu in menuItems' ng-click="showHideMenu($event);" >
<a class="iconsize" ui-sref={{mymenu.url}}>
<i class={{mymenu.image}}></i> <strong>{{mymenu.menuName}}</strong>
<span class="fa fa-chevron-down"></span>
</a>
<ul class="submenuHide">
<li class="" ng-repeat='submenu in mymenu.submenu'>
<a>{{submenu.submenuName}}</a>
</li>
</ul>
JS
$scope.showHideMenu = function(event) {
var classname = event.target.parentElement.parentElement.children[1].className;
if(classname == 'submenuHide'){
$(event.target.parentElement.parentElement.children[1]).removeClass('submenuHide');
$(event.target.parentElement.parentElement.children[1]).addClass('submenuShow');
}else if(classname == 'submenuShow'){
$(event.target.parentElement.parentElement.children[1]).removeClass('submenuShow');
$(event.target.parentElement.parentElement.children[1]).addClass('submenuHide');
}
}
A couple things. One, you'll need to make sure your menu is outside of the individual templates you're working with. Two, using ng-class bound to an ng-model ensures that your menu state is included in the digest cycle. This will save you the jqLite and dom parsing logic.
Here's an example plunker.
So your code might look like this:
<body ng-controller="MainCtrl">
<a ui-sref="hello">Hello</a>
<a ui-sref="about">About</a>
<button ng-click="toggleMenu()">Show / Hide Menu</button>
<ui-view></ui-view>
<ul ng-class="{showMenu: show, hideMenu: !show}">
<li ng-repeat="letter in ['a','b','c','d']">{{letter}}</li>
</ul>
</body>
With this JS:
app.controller('MainCtrl', function($scope) {
$scope.show = false;
$scope.toggleMenu = function() {
$scope.show = !$scope.show;
};
});
I've defined a navbar using angular and ui-bootstrap. There are several nav options which are rendered based on a login state:
If a user or admin is logged in, there should be a Logout option in the nav bar.
If an admin is logged in, the Admin option shows in the nav bar
If a user is logged in, no Admin appears in the navbar, instead a Profile link shows.
I'm using ui-router in this project, and according to the documentation, a parent state is never refreshed when a child state is, unless {refresh : true} is included in the state call. However, nothing refers to refreshing the state of one ui-view based on state of another.
The main index page of the app has two ui-view elements defined: one for nav, and the other for the application content:
<body ng-app="app">
<div ui-view="nav"></div>
<div class="container-fluid">
<div ui-view="appContent"></div>
</div>
</body>
What is occurring is when I click on a Logout nav option, I successfully log out but the nav options do not change unless I refresh the page.
My navbar template:
<div class="navbar-collapse collapse" collapse="navbarCollapsed">
<ul class="nav navbar-nav">
<li>
<a ui-sref="home">Home</a>
</li>
<li ng-if="!userState.isLoggedIn()">
<a ui-sref="login">Login</a>
</li>
<li ng-if="userState.isLoggedIn() && !userState.isAdmin()">
<a ui-sref="profile">Profile</a>
</li>
<li ng-if="userState.isAdmin()">
<a ui-sref="admin">Admin</a>
</li>
<li ng-if="userState.isLoggedIn()">
Logout
</li>
</ul>
</div>
and controller:
angular.module('navigation')
.controller('NavCtrl', function($rootScope, $scope, $state, $log, $sce, authenticationService, userService) {
"use strict";
var user = authenticationService.getUser();
$scope.userState = {
isAdmin : function() {
return user !== null && user.role === 'ADMIN';
},
isLoggedIn : function() {
return user !== null;
}
};
$scope.logoutUser = function() {
userService.clearLocalState();
$state.go('home', {}, {reload : true});
};
});
With the {reload : true} param option, I see a flicker in the nav element on logout, but the option visibility does not change. The only solution that works is a manual page refresh and I'd like to avoid that if at all possible.
Well, you have tovreload content of your navbar view. It's possible without the whole page refreshing. Use $state object. It has reload and other methods for manual manipulating of your views
Navbar template:
<div class="navbar-collapse collapse" collapse="navbarCollapsed">
<ul class="nav navbar-nav">
<li>
<a ui-sref="home">Home</a>
</li>
<li ng-if="!user">
<a ui-sref="login">Login</a>
</li>
<li ng-if="user && user.role !== 'ADMIN'">
<a ui-sref="profile">Profile</a>
</li>
<li ng-if="user.role === 'ADMIN'">
<a ui-sref="admin">Admin</a>
</li>
<li ng-if="user !== null">
Logout
</li>
</ul>
</div>
and controller:
angular.module('navigation')
.controller('NavCtrl', function($rootScope, $scope, $state, $log, $sce, authenticationService, userService) {
"use strict";
$scope.user = authenticationService.getUser();
$scope.logoutUser = function() {
userService.clearLocalState();
$scope.user = authenticationService.getUser();
//$state.go('home', {}, {reload : true});
};
});
It is a dirty "solution" but maybe a solution if you do not want to use $location reload.
I want to validate certain condition before the browser follow the link dynamically created by ui-router.
I was looking into $rootscope.$on('$stateChangeStart', ..) but I have no access to the controller.$scope from there. I also need to use this in several places in the application and would be cumbersome.
Keep in mind that ui-sref is linked to ui-sref-active (work together), so i can't remove ui-sref and, by say, to use $state.$go('some-state') inside a function called with ng-click.
The condition should be evaluated inside a $scope function and on on-click event (before-transition with the ability to cancel it)
I need something like this:
<li ui-sref-active="active">
<a ui-sref="somestate" ui-sref-if="model.validate()">Go Somestate</a>
</li>
I tried:
<li ui-sref-active="active">
<a ui-sref="somestate" ng-click="$event.preventDefault()">Go Somestate</a>
</li>
<li ui-sref-active="active">
<a ui-sref="somestate" ng-click="$event.stopImmediatePropagation()">Go Somestate</a>
</li>
And
<li ui-sref-active="active">
<a ui-sref="somestate">
<span ng-click="$event.stopPropagation();">Go Somestate</span>
</a>
</li>
Even
<li ui-sref-active="active">
<a ui-sref="somestate" onclick="return false;">Go Somestate</a>
</li>
But does not work.
SANDBOX
This answer inspired me to create a directive that allows me to interrupt the chain of events that end up changing state. For convenience and other uses also prevents the execution of ng-click on the same element.
javascript
module.directive('eatClickIf', ['$parse', '$rootScope',
function($parse, $rootScope) {
return {
// this ensure eatClickIf be compiled before ngClick
priority: 100,
restrict: 'A',
compile: function($element, attr) {
var fn = $parse(attr.eatClickIf);
return {
pre: function link(scope, element) {
var eventName = 'click';
element.on(eventName, function(event) {
var callback = function() {
if (fn(scope, {$event: event})) {
// prevents ng-click to be executed
event.stopImmediatePropagation();
// prevents href
event.preventDefault();
return false;
}
};
if ($rootScope.$$phase) {
scope.$evalAsync(callback);
} else {
scope.$apply(callback);
}
});
},
post: function() {}
}
}
}
}
]);
html
<li ui-sref-active="active">
<a ui-sref="somestate" eat-click-if="!model.isValid()">Go Somestate</a>
</li>
PLUNKER
You can use a scope function that will either returns :
no state
an existing state
like so :
HTML :
<li ui-sref-active="active">
<a ui-sref="{{checkCondition()}}">Go Somestate</a>
</li>
JS scope :
$scope.checkCondition = function() {
return model.validate()
? 'someState'
: '-' // hack: must return a non-empty string to prevent JS console error
}
href attribute will be created only when the function returns an existing state string.
Alternatively, you could do a (ugly) :
<li ui-sref-active="active">
<a ui-sref="somestate" ng-if="model.validate()">Go Somestate</a>
<span ng-if="!model.validate()">Go Somestate</span>
</li>
Hope this helps
The easiest workaround to conditionally achieve routing without tinkering with directives, scope etc was a workaround i found here - https://github.com/angular-ui/ui-router/issues/1489
<a ui-sref="{{condition ? '.childState' : '.'}}"> Conditional Link </a>
You can always double up on the element and show/hide conditionally
<li ui-sref-active="active">
<a ng-show="condition1" style="color: grey">Start</a>
<a ng-hide="condition1" ui-sref="start">Start</a>
</li>
http://plnkr.co/edit/ts4yGW?p=preview
No need for complicated directives or hacks. The following works fine and allows for specific handling on click of non-sref items:
<a
ng-repeat="item in items" ui-sref="{{item.sref || '-'}}"
ng-click="$ctrl.click(item, $event)"
>...</a>
And in the controller, a simple click handler for the items which don't have an item.sref:
this.click = function(item, event) {
if (!item.sref) {
event.preventDefault();
//do something else
}
};
Based on the answers to How to dynamically set the value of ui-sref you can create a function in your scope for building the URL:
$scope.buildUrl = function() {
return $state.href('somestate', {someParam: 'someValue});
};
And then conditionally append it to the link with ng-href
<a ng-href="{{ someCondition ? buildUrl() : undefined }}">Link</a>
As you can see in the demo below, ng-href does not add the href attribute if value is negative.
angular.module('app', [])
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
<a ng-href="{{ condition ? 'http://thecatapi.com/api/images/get?format=src&type=gif' : undefined}}">This is the link</a>
<br>
<label for="checkbox">
<input type="checkbox" id="checkbox" ng-model="condition">
Link active?
</label>
</div>
I know this is an old question, but for future reference I wanted to offer an alternative solution since I didn't see it in any of the answers so far.
Desired:
<li ui-sref-active="active">
<a ui-sref="somestate" ui-sref-if="model.validate()">Go Somestate</a>
</li>
Potential solution (template):
<li ng-class="{ active: state.current.name === 'somestate' }">
<a ng-click="navigateToState()">Go Somestate</a>
</li>
And in the controller:
$scope.state = $state;
$scope.navigateToState = navigateToState;
function navigateToState() {
if ($scope.model.valid) {
$state.go('somestate');
}
}
Possible solution for those who still need ng-click working on ui-sref component or its parents.
My solution is to use href instead of ui-sref and to modify Emanuel's directive a bit to be able to stop href and ng-click calls separately.
Planker.
Though it has a few restrictions:
will not work with ui-sref
you should have different urls for each state because of previous restriction
ui-sref-active will not work either
For the binary case (link is either enabled or disabled), it "now" (since ~2018) works like this (prevents the click and sets it to disabled):
<a ui-sref="go" ng-disabled="true">nogo</a>
and for other tags as well:
<span ui-sref="go" ng-disabled="true">nogo</span>
So here is my issue, I can't get my $scope.documents to update from my directive.$scope.parentUpdate({ documents: $scope.docType}) does not seem to execute at all an so my documents never updates. $scope.docType= resultgets all the data I need but it just does not push it back to the parent controller.
app.controller('docGridController',['$scope','getSideNav', 'getDocuments',
function($scope, getSideNav, getDocuments){
getSideNav().then(function(result){$scope.SideNav = result;
},
function(error){$scope.error = result;});
$scope.slideToggle = true;
$scope.documents=[];
$scope.update = function(k){
$scope.documents = k;
consle.log($scope.documents);
}}]);
app.directive('foSidenav',['getDocuments',function(getDocuments){
return{
replace: true,
scope: {
info:'=',
docType:'=',
parentUpdate:'&'
},
templateUrl:function(element,attr){
return attr.url;
},
controller: ['$scope','$element', '$attrs' ,'getDocuments',
function($scope,$element, $attrs, getDocuments){
$scope.selectDocType = function(id)
{
getDocuments(id).then(function(result){$scope.docType= result;
console.log($scope.docType);
alert('Printed results');
$scope.parentUpdate({ documents: $scope.docType});
},
function(error){$scope.error = result;});
};
}]
};
}]);
Here is the tag I am using in my template
<ul class="side-nav">
<li class="mainCat" ng-repeat=" item in info">
<a href="#" id="CatHeader" ng-click="slideToggle =! slideToggle">
<i class="{{item.displayIcon}} left-bar"></i>
<span ng-bind-html="item.label | html"></span>
</a>
<ul class="subCat slide-toggle" ng-show="slideToggle">
<li ng-repeat="subItem in item.docTypes">
<a href="#" ng-click="selectDocType(subItem.id)" >
<i class="fi-folder"></i>
<span ng-bind-html="subItem.Name | html"></span>
</a>
</li>
</ul>
</li>
and here is the directive call
<fo-sidenav url="{!URLFOR($Resource.FOPS_Resource, 'components/sidenav.html')}" info='SideNav' docType='documents' parentUpdate="update(documents)"></fo-sidenav>
Any ideas?? these scope are really throwing me off
I think the issue is with how you are using the variables from your isolated scope in the html. Now you are using them like below:
<fo-sidenav url="{!URLFOR($Resource.FOPS_Resource, 'components/sidenav.html')}"info='SideNav' docType='documents' parentUpdate="update(documents)"></fo-sidenav>
Try like the following:
<fo-sidenav url="{!URLFOR($Resource.FOPS_Resource, 'components/sidenav.html')}" info='SideNav' doc-type='documents' parent-update="update(documents)"></fo-sidenav>
For more information about read Matching Directives and Normalization sections here