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
Related
I'm new to Angular but enjoying getting to know it! I'm having no problem populating data but I'm stuck on this navigational problem.
I have a sidebar divided into two navigation s. Both are reading from nested JSON of 'areas' and 'reports'. The idea is that the currently selected/clicked item in the left hand list will populate the elements in the right hand list, according to its children in the JSON.
I've got the initial population working fine. It reads the 'areas' and assigns the active area, which displays the reports. I then have a directive which applies css classes on the click event, but how can I get it to also alter the active area?
I'm currently doing scope.activeArea = scope.areas[1];, but this does not work. The index is just there for development; that will be replaced by the index of the clicked element.
My HTML:
<div id="reports-subnav" ng-controller="reportAreasController">
<ul class="subnav-areas" >
<li ng-repeat="x in areas">
<a class="subnav-area-button" href="#" my-cool-directive>
<i class="{{ x.iconClass }}"></i>
</a>
</li>
</ul>
<ul id="nav-subsection" class="subnav-reports">
<li ng-repeat="r in activeArea.reports" id="{{ r.action }}" role="presentation"><span class="{{ r.iconClass }}"></span> {{ r.name }}</li>
</ul>
</div>
And Javascript:
var app = angular.module("focalWeb", []);
app.controller("reportAreasController", ['$scope','$http', function($scope, $http) {
$http.get('<?php echo SITE_ADDR; ?>/data/reportAreas.json').success (function(data){
$scope.areas = data.areas;
$scope.activeArea = $scope.areas[0];
});
}] );
app.directive("myCoolDirective", function() {
return {
restrict: "A",
link: function(scope, elem, attrs) {
$(elem).click(function() {
//var target = $(elem).next(".panel-collapse");
//target.hasClass("collapse") ? target.collapse("show") : target.collapse("hide");
$('.subnav-areas li').removeClass('active');
$(this).parent().addClass('active');
scope.activeArea = scope.areas[1];
});
}
}
});
Thanks!
Edit
Thanks to charlietfl's guidance my code now looks like this:
HTML
<div id="reports-subnav" ng-controller="reportAreasController">
<ul class="subnav-areas" >
<li class="subnav-area" ng-repeat="x in areas">
<a class="subnav-area-button" href="#" ng-click="showReports($event, x.id)">
<i class="{{ x.iconClass }}"></i>
</a>
</li>
</ul>
<ul id="nav-subsection" class="subnav-reports">
<li ng-repeat="r in activeArea.reports" id="{{ r.action }}" role="presentation"><span class="{{ r.iconClass }}"></span> {{ r.name }}</li>
</ul>
</div>
JS
var app = angular.module("focalWeb", []);
app.controller("reportAreasController", ['$scope','$http', function($scope, $http) {
$http.get('<?php echo SITE_ADDR; ?>/data/reportAreas.json').success (function(data){
$scope.areas = data.areas;
$scope.activeArea = $scope.areas[0];
$scope.showReports = function($event, index) {
$scope.activeArea = $scope.areas[index -1];
var theLi = angular.element($event.currentTarget).parent();
theLi.parent().find('li').removeClass('active');
theLi.addClass('active');
}
});
}] );
Edit 2
HTML
<div id="reports-subnav" ng-controller="reportAreasController">
<ul class="subnav-areas" >
<li class="subnav-area" ng-repeat="x in areas" ng-class="{active: x==activeArea}">
<a class="subnav-area-button" href="#" ng-click="showReports($event, x)">
<i class="{{ x.iconClass }}"></i>
</a>
</li>
</ul>
<ul id="nav-subsection" class="subnav-reports">
<li ng-repeat="r in activeArea.reports" id="{{ r.action }}" role="presentation"><span class="{{ r.iconClass }}"></span> {{ r.name }}</li>
</ul>
</div>
JS
var app = angular.module("focalWeb", []);
app.controller("reportAreasController", ['$scope','$http', function($scope, $http) {
$http.get('<?php echo SITE_ADDR; ?>/data/reportAreas.json').success (function(data){
$scope.areas = data.areas;
$scope.activeArea = $scope.areas[0];
$scope.showReports = function($event, item) {
$scope.activeArea = item;
}
});
}] );
Thanks again charlietfl!
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
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>
I'm having trouble replacing the {{path}} within the template tag in the ng-include. If I put the tag inside the template anywhere else works perfectly, however I need to work within the include-ng to get the base path. Anyone have an idea.
Html with MVC
<div ng-controller='menuCtrl'>
<nav id="menu">
<ul>
<li ng-repeat="item in itens" ng-include="'#(ViewBag.path)Areas/System/scripts/templates/menu.html'" repeat-done></li>
</ul>
</nav>
</div>
App.js
var myApp = angular.module('mobsupplyApp', []).directive('repeatDone', function () {
return function (scope) {
if (scope.$last === true) {
setTimeout(function () {
$("#menu").jarvismenu();
});
}
};
});
myApp.controller('menuCtrl', ['$scope', '$http', function ($scope, $http) {
$http.get(path + "api/menu").success(function (data, status) {
$scope.itens = data;
$scope.path = path;
});
}]);
Template.html
<i class="fa fa-lg fa-fw {{ item.image }}"></i> <span class="menu-item-parent">{{ item.description }}</span>
<ul ng-if="item.itens">
<li ng-repeat="item in item.itens" ng-include="'{{ path }}Areas/System/scripts/templates/menu.html'"></li>
</ul>
I have a simple list item being parsed with ng-repeat:
<ul>
<li ng-repeat="item in items" class="commonClass" ng-class="{'on': on_var}" ng-click="on_var=!on_var">
{{item.name}}
<li>
</ul>
clicking on a list-item will add the class name 'on' as expected. but I want to remove all other 'on' classes as soon as you click on another list-item and only add it to the one clicked. I come from a jQuery background and I am new to angular. All I want to do is something like this:
$("li.commanClass").click(function(){
$("li.commonClass").removeClass('on');
$(this).addClass('on');
})
I want to know what is the "angular way" of achieving this result
jsfiddle
Thanks.
In angular you want to pass object references to child scopes as much as possible. This way you can use prototypical object inheritance that can branch down many levels.
Think of a scope as being a model. In the case of ng-repeat it creates a child scope for each element. So if the parent scope holds a model it will be passed as reference to the child scopes. Each of these child scopes will hold a reference to the parent scope model object.
Thus you can do:
<li ng-repeat="item in model.items"
ng-class="{'on': model.selected==item}"
ng-click="model.selected=item">{{ item.name }}</li>
Then in controller:
$scope.model = {
selected: null,
items = [
{name: "Apple"},
{name: "Banana"},
{name: "California"}
]
};
Try to avoid using functions as above answer does. These functions will get called many times and will add unnecessary extra overhead.
Valuable post to read: "Thinking in AngularJS" if I have a jQuery background?
DEMO
You can add a variable to your scope to maintain which item is selected, and a function on your scope that toggles the variable.
Controller:
app.controller('myCtrl', function($scope) {
$scope.items =
[
{name: "Apple"},
{name: "Banana"},
{name: "California"}
]
$scope.selectItem = function( item ) {
$scope.selectedItem = item;
};
})
HTML:
<div ng-app="myApp">
<ul ng-controller="myCtrl">
<li ng-repeat="item in items" class="commonClass" ng-class="{'on': selectedItem === item}" ng-click="selectItem(item)">
{{ item.name }}
</li>
</ul>
</div>
Fiddle coming at ya of jsparks answer:
http://jsfiddle.net/eHDTF/
See fiddle for code!
I think you should refer a directive for whole your project instead of controller.
<div class="nav-item">
<ul>
<li active-me="on">Item 1</li>
<li active-me="on">Item 2</li>
<li active-me="on">Item 3</li>
<li active-me="on">Item 4</li>
<li active-me="on">Item 5</li>
</ul>
</div>
Then create activeMe directive:
angular.module('app').directive('activeMe', function(){
return {
restrict: 'A',
scope: {
activeMe: '#'
},
link: function(scope, element, attrs) {
element.bind('click', function() {
if (scope.activeMe !== undefined && scope.activeMe.trim().length > 0) {
element.parent().children().removeClass(scope.activeMe);
element.addClass(scope.activeMe);
} else {
element.parent().children().removeClass('active');
element.addClass('active');
}
});
}
};
});