Angular ensure toggle is closed if another one is opened - javascript

Have two different divs that can be toggled open and closed in Angualar app but trying to make sure that if one is opened that the other one closes. Seems like this should be simple enough in NG but still new to Angular. Anyone have any pointers?
Made an example fiddle here:
JS Fiddle
Here is the sample:
<body ng-app="simpleToggle">
<div ng-controller="AppCtrl">
<button ng-click="toggleCustom1()">Custom</button>
<span ng-hide="custom1">
<h2>Custom 1 is showing but Custom 2 should not be if it was already opened</h2>
</span>
<span ng-show="custom1"></span>
</div>
<div ng-controller="App2Ctrl">
<button ng-click="toggleCustom2()">Custom2</button>
<span ng-hide="custom2">
<h2>Custom 2 is showing but Custom 1 should not be if it was already opened.</h2>
</span>
<span ng-show="custom2"></span>
</div>
</body>
angular.module('simpleToggle', [])
.controller('AppCtrl',['$scope', function($scope){
$scope.custom1 = true;
$scope.toggleCustom1 = function() {
$scope.custom1 = $scope.custom1 === false ? true: false;
};
}])
.controller('App2Ctrl',['$scope', function($scope){
$scope.custom2 = true;
$scope.toggleCustom2 = function() {
$scope.custom2 = $scope.custom2 === false ? true: false;
};
}]);

Here you are dealing with the scope hierarchy, you wil want to use one of the mechanisms to coordinate between controllers. Some options are:
Using the $rootScope
Using messages
I have updated your example to use $rootScope here http://jsfiddle.net/4q7hrpc5/3/
firstly, create something to initialize the $rootScope. I created an outer controller and wrapped the two other controllers in that controller. Here is the updated HTML:
<body ng-app="simpleToggle">
<div ng-controller="OuterCtrl">
<div ng-controller="AppCtrl">
<button ng-click="toggleCustom1()">Custom</button>
<span ng-hide="!custom1">
<h2>Custom 1 is showing but Custom 2 should not be if it was already opened</h2>
</span>
</div>
<div ng-controller="App2Ctrl">
<button ng-click="toggleCustom2()">Custom2</button>
<span ng-hide="!custom2">
<h2>Custom 2 is showing but Custom 1 should not be if it was already opened.</h2>
</span>
</div>
</div>
</body>
here is the code for the controllers:
angular.module('simpleToggle', [])
.controller('OuterCtrl', ['$rootScope', function($rootScope) {
$rootScope.custom1 = false;
$rootScope.custom2 = false;
}])
.controller('AppCtrl',['$rootScope', '$scope', function($rootScope, $scope){
$scope.toggleCustom1 = function() {
$rootScope.custom1 = !$rootScope.custom1;
$rootScope.custom2 = false;
};
}])
.controller('App2Ctrl',['$rootScope', '$scope', function($rootScope, $scope){
$scope.toggleCustom2 = function() {
$rootScope.custom2 = !$rootScope.custom2;
$rootScope.custom1 = false;
};
}]);
Now this specific technique only works well for a small number of things that have to be coordinated. Messages or a service might be better if you have a large number of these things that need to be coordinated. Another alternative would be to put them all into the same controller.

It is a good practice for handling DOM Stuffs using directive. I use .next() for getting the next span. or you can use other selector for getting it.
documentation for elem: https://docs.angularjs.org/api/ng/function/angular.element
working here here
html:
<body ng-controller="MainCtrl">
<div>
<button change-toggle>Custom</button>
<span id="span1" class="toggle-show-css">
<h2>Custom 1 is showing but Custom 2 should not be if it was already opened</h2>
</span>
</div>
<div>
<button change-toggle>Custom2</button>
<span id="span2" class="toggle-show-css">
<h2>Custom 2 is showing but Custom 1 should not be if it was already opened.</h2>
</span>
</div>
</body>
css
.toggle-hide-css {
visibility: hidden;
}
.toggle-show-css {
visibility: visible;
}
directive
app.directive('changeToggle', ['$location', function($location) {
return {
restrict: 'A',
link: function(scope, elem, attrs) {
elem.bind('click', function(event) {
var spanner = elem.next();
if(spanner.hasClass("toggle-show-css")) {
elem.parent().parent().find('span').removeClass("toggle-show-css");
elem.parent().parent().find('span').addClass("toggle-hide-css");
spanner.removeClass("toggle-show-css");
spanner.addClass("toggle-hide-css");
} else {
elem.parent().parent().find('span').removeClass("toggle-show-css");
elem.parent().parent().find('span').addClass("toggle-hide-css");
spanner.removeClass("toggle-hide-css");
spanner.addClass("toggle-show-css");
}
});
}
}
}]);

Related

Setting a scope variable from a directive with AngularJS

I've gone through what must be 20 similar questions asked on SO but have yet to find a solution for my situation, so I hope you guys can help me out.
The goal is to sort the list of names by the property that's provided in the "sort-type" attribute of the directive, but only for the list within each controller (not all lists at the same time).
HTML
<div ng-controller="TestController as testOne">
<b>By:</b> {{testOne.sortType}}<br>
<b>Reverse:</b> {{testOne.sortReverse}}<br>
<div ng-repeat="item in testOne.list">
<p table-sort sort-type="name" sort-reverse="false">Sort by name</p>
<ul>
<li ng-repeat="childItem in testOne.childList | orderBy:testOne.sortType">{{childItem.name}}</li>
</ul>
</div>
</div>
<br><br>
<div ng-controller="TestController as testTwo">
<b>By:</b> {{testTwo.sortType}}<br>
<b>Reverse:</b> {{testTwo.sortReverse}}<br>
<div ng-repeat="item in testTwo.list">
<p table-sort sort-type="name" sort-reverse="false">Sort by name</p>
<ul>
<li ng-repeat="childItem in testTwo.childList | orderBy:testTwo.sortType">{{childItem.name}}</li>
</ul>
</div>
</div>
Javascript (Angular)
var app = angular.module('demo', []);
app.controller('TestController', TestController);
function TestController() {
var vm = this;
vm.sortType = 'oldOrder';
vm.sortReverse = false;
vm.list = [1];
vm.childList = [{ name: 'Jimmy' },
{ name: 'Danny' },
{ name: 'Bobby' }];
}
/////////////////////////////////////
app.directive('tableSort', tableSort);
function tableSort() {
var directive = {
restrict: 'A',
link: linkFunc,
};
return directive;
function linkFunc(scope, element, attr) {
element.on('click', function() {
if(scope.sortType === attr.sortType) {
scope.sortReverse = !scope.sortReverse;
} else {
scope.sortType = attr.sortType;
}
});
}
}
JSFiddle here
My actual application is a bit more complex but I've tried to abstract it as much as possible.
Thanks for looking :)
Ok Several things going on here:
you are using the controllerAs syntax on your templates but
you are changing scope variables in your directive. hence your
controller variables are never changed.
your directive is inside of the ng-repeat which means that
you are actuating actually on a child scope so if you are setting
variables directive on the scope your ng-repeat won't be able to
reach them because they are being set after the child scope are
created.
you are using element.on which executes outside of angular
digest which means you would have to call scope.$apply to let
angular know that something happened.
Take a look at this
https://jsfiddle.net/rez8ey12/
i hope it helps

AngularJS, how to trigger dom-related javascript on ng-if change

I have a form field (input text) with an ng-if being false at the begining. At some point the ng-if value become true.
When this happen, I want to execute some javascript which manipulate the DOM. To keep it simple, let's say that I need to select the input value and focus the field.
<input type="text" ng-value="foo" ng-if="show" onshow="doSomething()"/>
<button ng-click="toggle()"></button>
The JavaScript
ctrl.foo = "bar";
ctrl.show = false;
ctrl.toggle = function(){
ctrl.show = !ctrl.show;
}
I know that it looks like a "non-angular approach", but here I think the action is not model related.
Since the ng-if directive execute the template each time show become true, you can use ng-init for that. See the following snippet and replace alert('test); by anything you want.
angular.module('test', []).controller('test', function($scope, $element) {
$scope.show = false;
$scope.toggle = function() {
$scope.show = !$scope.show;
};
$scope.init = function() {
alert($element.find('input').val());
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="test">
<div ng-controller="test">
<input type="text" value="foo" ng-if="show" ng-init="init()" />
<button ng-click="toggle()">Toggle</button>
</div>
</div>

Get the value of the clicked element on angularjs

I started working with angular about 2 days ago. I'm still wrapping my head around how to do many things. Right now, I am trying to have a tooltip appear with the information pertaining the clicked "tag". I defined a "tag" as a <span> element with a ng-toolkitlike so:
<div id="list-of-words" ng-controller="inlineEditorController" ng-click="hideTooltip()">
<!-- This is the tooltip. It is shown only when the showtooltip variable is truthful -->
<div id="tooltip" class="tooltip" ng-click="$event.stopPropagation()" ng-show="showtooltip">
<input type="text" ng-model="value" />
</div>
<div ng-repeat="w in words">
<span class="tag" ng-click="toggleTooltip($event)">{{w.content}}</span>
</div>
</div>
my controller is as such:
var app = angular.module('myApp', []);
app.controller('inlineEditorController', ['$scope', function($scope){
$scope.inlineEditor = function(){
$scope.showtooltip = false;
$scope.value = 'Edit me.';
$scope.hideTooltip = function(){
$scope.showtooltip = false;
}
$scope.toggleTooltip = function(e){
e.stopPropagation();
$scope.showtooltip = !$scope.showtooltip;
}
};
})]);
what I'm attempting to do is to change the 'Edit me.' to display the content from {{w.content}} but I have no idea how to do this. Everything I've tried so far has failed.
To get item content you can use:
<span class="tag" ng-click="toggleTooltip($event,w.content)">{{w.content}}</span>
So, controller should be:
$scope.toggleTooltip = function(e,content){
e.stopPropagation();
$scope.showtooltip = !$scope.showtooltip;
$scope.value = content;
}
You can use $event.currentTarget to get the element. i.e. e.currentTarget in your controller.

Use Directive two times in one controller AngularJS

I want to use same directives multiple times in one controller in AngularJS. I want to create a list widget that can be used multiple times. I can display two widgets at the same time under the same controller. But, I am unable to bind teamA and teamB data to ng-repeat in my directive. In addition to that, the code fails during addTeamMember() because datasource is undefined. I was hoping that datasource will be updated with teamA and teamB respectively.
Here is the HTML code.
<div ng-controller="myCtrl"><div class="container">
<my-directive datasource="model.teamA"></my-directive>
<my-directive datasource="model.teamB"></my-directive>
</div></div>
Controller.js:
angular.module('app',[])
.controller('myCtrl', [ '$scope', function($scope){
$scope.teamA = {};
$scope.teamB = {};
} ] );
Directive.js:
angular.module('app', [] )
.directive('myDirective', function(){
return{
restrict: 'AE',
templateUrl: 'directive_html.html',
scope: {
datasource: "=TeamMembers"
},
transclude: true,
link: function(scope, elem, attrs){
scope.addTeamMember = function(){
scope.datasource.push({});
};
scope.removeTeamMember = function(item){
scope.datasource.splice(item, 1);
};
}
};
}) ;
directive_html.html:
<div><div class="container">
<div class="form-group" ng-repeat="member in TeamMembers">
<textarea ng-model="member.text" rows="2"></textarea>
Remove
<div>
<button type="button" ng-click="addTeamMember()">Add</button>
</div></div>
Could anyone please help me out here? I want to create custom Widgets that can be used multiple places either in same controllers or in different controllers.
Thanks
As #Neozaru pointed out in comments. You are expecting the directive attribute to be called team-members here:
<div ng-controller="myCtrl"><div class="container">
<my-directive team-members="model.teamA"></my-directive>
<my-directive team-members="model.teamB"></my-directive>
</div></div>
You do this when you define the isolated scope as:
scope: {
datasource: "=TeamMembers"
}
The above line is saying, "team-members is what the outside attribute will be named, but internally I'll refer to the referenced object as scope.datasource".

AngularJS directive - ng-class in ng- repeat should it be a $watcher to toggle style?

I am currently implementing a spike to further my understanding on angular directives etc.
The premise is to create a FX watch list on a number of currency pairs.
My data feed is set up for my price updates via socket.io.
The stumbling block that i have is being able to change the css dependent on price change ie up arrow for up, down arrow for down.
I feel a watcher function is what i need but struggled on where to start so was looking for some sort of expression in ng-class to do the job ... but the method not only started to look like a $watcher it was also flawed as saving the previous price to scope on my directive meant there was only ever one old value not one for each price.
There for my question is : Is the solution with ng-class or in setting up a $watcher function ?
Heres my code ...
HTML template
<div ng-repeat="rate in rates" ng-click="symbolSelected(rate)">
<div class="col-1-4">
{{rate.symbol}}
</div>
<div class="col-1-4">
<span ng-class='bullBear(rate.bidPoint)' ></span> {{rate.bidBig}}<span class="point">{{rate.bidPoint}}</span>
</div>
<div class="col-1-4">
<span ng-class='bullBear(rate.offerPoint)' ></span> {{rate.offerBig}}<span class="point">{{rate.offerPoint}}</span>
</div>
<div class="col-1-4">
{{rate.timeStamp | date : 'hh:mm:ss'}}
</div>
</div>
My directive currently looks like this ... as noted this will not work and the bullBear method was starting to look like a $watcher function.
.directive('fxmarketWatch', function(fxMarketWatchPriceService){
return {
restrict:'E',
replace:'true',
scope: { },
templateUrl:'common/directives/fxMarketWatch/marketwatch.tpl.html',
controller : function($scope, SYMBOL_SELECTED_EVT,fxMarketWatchPriceService){
$scope.symbolSelected = function(currency){
$scope.$emit(SYMBOL_SELECTED_EVT,currency);
}
$scope.bullBear = function(newPrice){
if ($scope.oldPrice> newPrice ){
return ['glyphicon glyphicon-arrow-down','priceDown'];
}
else if ($scope.oldPrice > newPrice ){
return ['glyphicon glyphicon-arrow-up','priceUp'];
}
}
$scope.$on('socket:fxPriceUpdate', function(event, data) {
$scope.rates = data.payload;
});
}
}
})
You could modify the ng-class and move the logic into the view, because styling and placing classes shouldn't be done in code.
<div class="col-1-4">
<span class="glyphicon" ng-class="{'glyphicon-arrow-up priceUp': oldPrice > rate.bidPoint, 'glyphicon-arrow-down priceDown':oldPrice > rate.bidPoint}"></span> {{rate.bidBig}}<span class="point">{{rate.bidPoint}}</span>
</div>
Or like this:
<span class="glyphicon {{oldPrice > rate.bidPoint ? 'glyphicon-arrow-down priceDown':'glyphicon-arrow-up priceUp'}}></span> {{rate.bidBig}}<span class="point">{{rate.bidPoint}}</span>
I will recommend you to use both ng-class and $watcher. The two can actually compliment each other:
UPDATE: To make the code works with ng-repeat, we need to migrate all of CSS classes logic to another controller:
app.controller('PriceController', function($scope) {
// we first start off as neither up or down
$scope.cssBid = 'glyphicon';
$scope.cssOffer = 'glyphicon';
var cssSetter = function(newVal, oldVal, varName) {
if (angular.isDefined(oldVal) && angular.isDefined(newVal)) {
if (oldVal > newVal) {
$scope[varName] = 'glyphicon glyphicon-arrow-down priceDown';
} else if (newVal > oldVal) {
$scope[varName] = 'glyphicon glyphicon-arrow-up priceUp';
} else {
$scope[varName] = 'glyphicon';
}
}
};
// watch for change in 'rate.bidPoint'
$scope.$watch('rate.bidPoint', function(newVal, oldVal) {
cssSetter(newVal, oldVal, 'cssBid');
});
// watch for change in 'rate.offerPoint'
$scope.$watch('rate.offerPoint', function(newVal, oldVal) {
cssSetter(newVal, oldVal, 'cssOffer');
});
});
Next, we bind this PriceController onto ng-repeat div. By doing so, Angular will create one controller instance for each rate in rates. So this time rate.bidPoint and rate.offerPoint should be available for $watch-ing:
<div ng-repeat="rate in rates" ng-click="symbolSelected(rate)" ng-controller="PriceController">
<div class="col-1-4">
<span ng-class='cssBid'></span> {{rate.bidBig}}<span class="point">{{rate.bidPoint}}</span>
</div>
<div class="col-1-4">
<span ng-class='cssOffer'></span> {{rate.offerBig}}<span class="point">{{rate.offerPoint}}</span>
</div>
</div>
Now, directive's controller will be much shorter than before:
controller: function($scope, SYMBOL_SELECTED_EVT, fxMarketWatchPriceService){
$scope.symbolSelected = function(currency) {
$scope.$emit(SYMBOL_SELECTED_EVT, currency);
}
$scope.$on('socket:fxPriceUpdate', function(event, data) {
$scope.rates = data.payload;
});
}

Categories

Resources