AngularJs accordion not opening with is-open attribute - angularjs

I encountered a strange problem. I had written my own directive. I am trying to use
is-open
attribute to specify when accordion group needs to be opened based on model named $scope.state. Unfortunately it's property isOpen is always false, even when it is changing to true in checked() function.
Directive.html
<accordion>
<accordion-group class="my-panel-body" ng-class="{'my-panel-heading-checked':state.isOpen,'my-panel-heading-default':!state.isOpen}" is-open="state.isOpen">
<!--Accordion header -->
<accordion-heading id="{{groupId}}" >
<div ng-click="checked()" >
<i class="pull-left glyphicon" ng-class="status"></i> {{title}}
</div>
</accordion-heading>
<!--Accordion content -->
</accordion-group>
</accordion>
Directive.js
angular.module('locale').
directive('accordionList', function () {
return{
restrict: 'A',
scope: {
title: '=',
result: '=',
groupId: '='
},
templateUrl: 'html/accordionList.html',
controller: function($scope){
var glyphicon = 'glyphicon glyphicon-';
var status = glyphicon + 'unchecked';
$scope.state = {
isOpen: false
}
$scope.status = status;
$scope.checked = function(){
console.log($scope.state.isOpen) //It is always false!
if ($scope.state.isOpen) {
$scope.state.isOpen = false;
$scope.status = status;
}
else { //Always entering this part of code!
console.log("FLAG");
$scope.state.isOpen = true; //True only here when invoked.
$scope.status = glyphicon + 'check';
console.log($scope.state.isOpen);
}
}
}
};
});
I would be grateful for any help. I have no idea what is not working.

Based on your provided code, I created a plunker to reproduce your issue and it does show the problem.
The fact is that the accordion-group directive will auto bind a toggleOpen function to click event (see the template). In this case, with your own checked function your isOpen value is changed twice when element get clicked, and thus remains unchanged.
so just eliminate the 'check()' or stop changing isOpen value in it.
$scope.checked = function(){
console.log($scope.state.isOpen) //It is always false!
if ($scope.state.isOpen) {
//$scope.state.isOpen = false;
$scope.status = status;
}
else { //Always entering this part of code!
console.log("FLAG");
// $scope.state.isOpen = true; //True only here when invoked.
$scope.status = glyphicon + 'check';
console.log($scope.state.isOpen);
}
}

Related

data-ng-class conditions is not working when changing url manually

I did a conditional statement on the div on data-ng-class and I want to somehow add this scope to the controller and specify when the url is /vegetarian to apply class 'egg' and i manually change url i want the class 'bacon' to be applied, but its not working, where I have i gone wrong?
html:
<div data-ng-class="{'egg': isVeggie, 'bacon': !isVeggie }">
Text here
</div>
controller:
app.controller('foodCtrl', function ($scope, $location) {
$scope.location = $location;
$scope.isVeggie = false;
$scope.isVeggie = function() {
if(isVeggie === $location.path('/vegetarian')) {
return true;
}
}
});
this is not working. When I type /vegetarian, the class egg is being applied, but when I change the url to something else like /breakfast, the class 'egg' remains. How can I make this work?
Thanks
Why the property and the function with the same name ? After all you will have only the function
$scope.isVeggie = false;
$scope.isVeggie = function() {
if(isVeggie === $location.path('/vegetarian')) {
return true;
}
}
Change your code to have only the function.
Only
$scope.isVeggie = function() {
if(isVeggie === $location.path('/vegetarian')) {
return true;
}
}
and markup
<div data-ng-class="{'egg': isVeggie(), 'bacon': !isVeggie() }">
Text here
</div>
You bind you ng-class directive to the property isVeggie and not the function isVeggie().
Change your html to:
<div data-ng-class="{'egg': isVeggie(), 'bacon': !isVeggie() }">
Text here
</div>
You can then remove the following line in your controller:
$scope.isVeggie = false;

AngularJS - ng-if runs digest loop

I am facing problem with infinite loop on loading the view. The data is loaded from an API call using ngResource in the controller. The view seems to be reloaded multiple times before rendering the view correctly. I use ng directives in the template calling scope methods and this seems to get into loop causing the view to be re-rendered.
Here is my Controller
.controller('IndexCtrl', ['$scope', '$stateParams', 'ProfileInfo',
function($scope, $stateParams, ProfileInfo) {
$scope.navTitle = 'Profile Information';
$scope.data = {};
ProfileInfo.query({
id: $stateParams.id
}).$promise.then(function(Profile) {
if (Profile.status == 200) {
$scope.data.Profile = Profile.data[0];
}else{
console.log(Profile.status);
}
}, function(error) {
console.log(error);
});
$scope.showImageBlock = function(object, image) {
if (object.hasOwnProperty('type') && object.type == 'image') {
imageReference = object.value;
var imageUrl;
angular.forEach(image, function(value, key) {
if (value.id == imageReference) {
$scope.data.imageUrl = value.graphic.url;
return;
}
});
}
return object.hasOwnProperty('type') && object.type == 'image';
};
$scope.showText = function(object) {
console.log('text');
return object.hasOwnProperty('type') && object.type == 'text';
};
}
])
And Here is my template
<ion-view cache-view="false">
<ion-nav-title>
{{navTitle}}
</ion-nav-title>
<div class="bar bar-subheader bar-light">
<h2 class="title">{{navSubTitle}}</h2>
</div>
<ion-content has-header="true" padding="true" has-tabs="true" class="has-subheader">
<div ng-repeat="profileInfo in data.Profile">
<div class="list">
<img ng-if="showImageBlock(profileInfo,data.Profile.images)" ng-src="{{ data.imageUrl }}" class="image-list-thumb" />
<div ng-if="showText(profileInfo)">
<a class="item">
{{profileInfo.name}}
<span ng-if="profileInfo.description.length != 0"class="item-note">
{{profileInfo.description}}
</span>
</a>
</div>
</div>
</div>
</ion-content>
Here is the output of console window when tried log the number of times showText function is called.
The actual result from ngResource call has only 9 items in array but it loops more than 9 times and also multiple loops. This happens for a while and stops. Could anyone please point me in the right direction in fixing it.
Thank you
Finally I ended up creating a custom directive which does the function of ng-if without the watchers which triggers the digest loop. It's not a pretty solution but it seems to do the job as expected. I copied the code of ng-if and removed the $scope watcher. Here is the custom directive.
angular.module('custom.directives', [])
.directive('customIf', ['$animate',function($animate) {
return {
multiElement: true,
transclude: 'element',
priority: 600,
terminal: true,
restrict: 'A',
$$tlb: true,
link: function($scope, $element, $attr, ctrl, $transclude) {
var block, childScope, previousElements;
value = $scope.$eval($attr.customIf);
if (value) {
if (!childScope) {
$transclude(function(clone, newScope) {
childScope = newScope;
clone[clone.length++] = document.createComment(' end customIf: ' + $attr.customIf + ' ');
block = {
clone: clone
};
$animate.enter(clone, $element.parent(), $element);
});
}
}
else {
if (previousElements) {
previousElements.remove();
previousElements = null;
}
if (childScope) {
childScope.$destroy();
childScope = null;
}
if (block) {
previousElements = getBlockNodes(block.clone);
$animate.leave(previousElements).then(function() {
previousElements = null;
});
block = null;
}
}
}
};
}]);
This allows us to use the customIf as follows
<div custom-if="showText(profileInfo)">

How to change select functions in Angular Directive?

http://plnkr.co/edit/pJRzKn2v1s865w5WZBkR?p=preview
I have a large select dropdown form which is repeated in 2 places. The only thing that changes is the first select tag, which has a different function.
<!--
On simple, change ng-change function to functionOne
On advanced, change ng-change function to functionTwo
-->
<select name="name1" ng-change="functionOne('function1')" id="the-id-1">
<select name="name2" ng-change="functionTwo('function2)" id="the-id-2">
<option value="aaa">aaa</option>
<option value="bbb">bbb</option>
<option value="ccc">ccc</option>
</select>
I tried using ng-hide ng-show however there must be a different way to accomplish this.
var app = angular.module('myApp', [])
.directive('termsForm', function() {
return {
templateUrl : "termsForm.html",
restrict : "E",
scope : false,
controller : 'TermsFormController'
}
})
.directive('selectOptions', function() {
return {
templateUrl : "form.html",
restrict : "E",
scope : false
}
})
.controller('TermsFormController',
['$scope',
function($scope) {
var vs = $scope;
vs.hello = "This is the form.";
vs.showingSimple = true;
vs.showingAdvanced = false;
vs.showForm = function(type) {
if (type === 'simple') {
vs.showingSimple = true;
vs.showingAdvanced = false;
} else if (type === 'advanced') {
vs.showingSimple = false;
vs.showingAdvanced = true;
}
}
vs.functionOne = function(msg) {
alert(msg);
}
vs.functionTwo = function(msg) {
alert(msg);
}
}]);
termsForm.html
<ul class="nav nav-tabs">
<button class="btn btn-info" ng-click="showForm('simple')">Simple</button>
<button class="btn btn-info" ng-click="showForm('advanced')">Advanced</button>
</ul>
<p>The select:</p>
<div ng-show="showingSimple" class="simple-form">
<p>Simple</p>
<select-options></select-options>
</div>
<div ng-show="showingAdvanced" class="advanced-form">
<p>Advanced</p>
<select-options></select-options>
</div>
You already have a directive created for your select, that gets you half way there. Now you just need to pass the function in through whats known as the isolated scope.
.directive('selectOptions', function() {
return {
templateUrl : "form.html",
restrict : "E",
scope : {
changeFunc: '&'
}
}
})
This allows you to pass in the function you want to call on the ng-change event:
<select-options changeFunc="function1"></select-options>
<select-options changeFunc="function2"></select-options>
And then in your form.html you simply put
<select name="name2" ng-change="changeFunc()" id="the-id-2">
This way you are basically passing the funciton in as a parameter. Read this blog for a great guide on isolated scopes.
I would just refactor your markup and controller to adapt based on the simple/advanced context.
In your controller, you'd expose a 'generic' on change function for the dropdown, first...
(function () {
'use strict';
angular.module('app').controller('someCtrl', [someCtrl]);
function someCtrl() {
var vm = this;
vm.isSimple = true;
vm.nameChange = function () {
if(vm.isSimple)
functionOne('function1');
else
functionTwo('function2');
}
// Other things go here.
}
})();
...Then, on your view, your select would change to this*:
<select id="someId" name="someName" ng-change="vm.nameChange()" />
*: Assuming you're using controllerAs syntax, that is. If you're not, don't prepend the vm. on the select.

Ng-class not working

<div ng-controller="TestingCtrl">
<input ng-class="{'fancy' :status}">
<input ng-class="{'fancy' :status2}">
<input type="button" value="change" ng-change="action()">
</div>
.fancy{
background:Yellow;
}
function TestingCtrl($scope) {
$scope.status = true
$scope.status2 = true
$scope.action = function() {
$scope.status = false
$scope.status2 = false
}
The model never changes so ng-change is never called. From the docs:
"ngChange expression is only evaluated when a change in the input value causes a new value to be committed to the model"
The action function is never evaluated because there's no ng-model to change for that specific input.
If you do ng-click it will work, then you can toggle the background color.
$scope.action = function(){
$scope.status = !$scope.status
$scope.status2 = !$scope.status2
}
http://jsfiddle.net/KYk2c/
Use ng-click instead of ng-change on button and it's done.. Also Created an angular app first before your controller..
var app = angular.module('myApp', []);
function testController($scope) {
$scope.status = true;
$scope.status2 = true;
$scope.action = function() {
$scope.status = false
$scope.status2 = false
}
}
and the html goes like this
<div ng-controller="testController">
<input ng-class="{'fancy' :status}">
<input ng-class="{'fancy' :status2}">
<input type="button" value="change" ng-click="action()">
</div>

Create Hoverable popover using angular-ui-bootstrap

I have the following code for creating a popover in my template file:
<span class="icon-globe visibility"
id="visibilityFor{{post.metaData.assetId}}"
popover="{{post.visibilityListStr}}"
popover-placement="right"
popover-trigger="mouseenter"
popover-popup-delay="50"
visibility>
</span>
I have a few clickable links on the popover. But the problem is I'm not able to hover on the popover created. I referred to the link http://jsfiddle.net/xZxkq/
and tried to create a directive viz. 'visibility' for this purpose.
Here is the code:
myAppModule.directive("visibility", function ($timeout,$rootScope) {
return {
controller: function ($scope, $element) {
$scope.attachEvents = function (element) {
$('.popover').on('mouseenter', function () {
$rootScope.insidePopover = true;
});
$('.popover').on('mouseleave', function () {
$rootScope.insidePopover = false;
$(element).popover('hide');
});
}
},
link: function (scope, element, attrs) {
$rootScope.insidePopover = false;
element.bind('mouseenter', function (e) {
$timeout(function () {
if (!$rootScope.insidePopover) {
element.popover('show');
attachEvents(element);
}
}, 200);
});
element.bind('mouseout', function (e) {
$timeout(function () {
if (!$rootScope.insidePopover) {
element.popover('show');
attachEvents(element);
}
}, 200);
});
}
}
});
But I get an exception for 'element.popover' since it is undefined. Please point as to what I'm doing wrong and how can I show/hide the angular ui popover from the directive. I am using angular ui bootstrap JS file.
I have solved it in a very cleaned way and thought to share it:
.popover is being created not as a child of the uib-popover
so the idea is to wrap uib-popover with a parent and to control show&hide on hovering the parent.
.popover and uib-popover are children of this parent
so just left to set popover-trigger=none and you have what you are wishing for.
I created a plunk example:
<span ng-init="popoverOpened=false" ng-mouseover="popoverOpened=true" ng-mouseleave="popoverOpened=false">
<button class="btn btn-default" uib-popover-html="htmlPopover"
popover-trigger="none" popover-placement="bottom-left" popover-is-open="popoverOpened" >
<span>hover me</span>
</button>
</span>
enjoy.
I don't know if this is relevant to the OP anymore, but I've had the same problem and fortunately I managed to solve it.
Undefined error
First thing first, the undefined error you are getting might be (at least in my case) because you are using the development version of ui-bootstrap. In my case I got this error when trying to bind element.popover. After adding the minified version of the library the error went away.
Keep the popover open when hovering over it
To do this I have created a custom directive that makes use of the popover from the ui-bootstrap library.
Directive
app.directive('hoverPopover', function ($compile, $templateCache, $timeout, $rootScope) {
var getTemplate = function (contentType) {
return $templateCache.get('popoverTemplate.html');
};
return {
restrict: 'A',
link: function (scope, element, attrs) {
var content = getTemplate();
$rootScope.insidePopover = false;
$(element).popover({
content: content,
placement: 'top',
html: true
});
$(element).bind('mouseenter', function (e) {
$timeout(function () {
if (!$rootScope.insidePopover) {
$(element).popover('show');
scope.attachEvents(element);
}
}, 200);
});
$(element).bind('mouseleave', function (e) {
$timeout(function () {
if (!$rootScope.insidePopover)
$(element).popover('hide');
}, 400);
});
},
controller: function ($scope, $element) {
$scope.attachEvents = function (element) {
$('.popover').on('mouseenter', function () {
$rootScope.insidePopover = true;
});
$('.popover').on('mouseleave', function () {
$rootScope.insidePopover = false;
$(element).popover('hide');
});
}
}
};
});
This directive also accepts a custom template for the popover, so you are not limited to just title and some text in it. You can create your own html template and feed it to the control.
Usage
<a href="#" hover-popover>Click here</a>
Hopes this helps someone else in the future :)
Edit
As requested, here is a Fiddle link. It lacks the styling, but it should demonstrate the way it works.
There I spend 1 day and finally get solution.
<button uib-popover="{{dynamicPopover.content}}"
popover-trigger="outsideClick" popover-is-open="popoverIsOpen"
ng-mouseenter="popoverIsOpen = !popoverIsOpen"
popover-title="{{dynamicPopover.title}}" type="button" class="btn btn-default">Dynamic Popover</button>
Please check
Plunkeer Link
Check only Dynamic Popover button code
Thanks,
I think Cosmin has the hoverable popover right, but it does seem to be using the Twitter Bootstrap popover method. The idea is to have this hoverable popover implemented only with AngularJS and one of the Bootstrap wrappers for AngularJS, which are UI Bootstrap or AngularStrap.
So I have put together an implementation which uses only AngularStrap:
myApp.directive('hoverablePopover', function ($rootScope, $timeout, $popover) {
return {
restrict: "A",
link: function (scope, element, attrs) {
element.bind('mouseenter', function (e) {
$timeout(function () {
if (!scope.insidePopover) {
scope.popover.show();
scope.attachEventsToPopoverContent();
}
}, 200);
});
element.bind('mouseout', function (e) {
$timeout(function () {
if (!scope.insidePopover) {
scope.popover.hide();
}
}, 400);
});
},
controller: function ($scope, $element, $attrs) {
//The $attrs will server as the options to the $popover.
//We also need to pass the scope so that scope expressions are supported in the popover attributes
//like title and content.
$attrs.scope = $scope;
var popover = $popover($element, $attrs);
$scope.popover = popover;
$scope.insidePopover = false;
$scope.attachEventsToPopoverContent = function () {
$($scope.popover.$element).on('mouseenter', function () {
$scope.insidePopover = true;
});
$($scope.popover.$element).on('mouseleave', function () {
$scope.insidePopover = false;
$scope.popover.hide();
});
};
}
};
});
When you have a popover element, you need to take into account that you have the element that triggers the popover and you also have the element with the actual popover content.
The idea is to keep the popover open when you mouse over the element with the actual popover content. In the case of my directive, the link function takes care of the element that triggers the popover and attaches the mouseenter/mouseout event handlers.
The controller takes care of setting the scope and the popover itself via the AngularStrap $popover service. The controller adds the popover object returned by the AngularStrap service on the scope so that it is available in the link function. It also adds a method attachEventsToPopoverContent, which attaches the mouseenter/mouseout events to the element with the popover content.
The usage of this directive is like this:
<a title="Popover Title" data-placement="left" data-trigger="manual" data-content="{{someScopeObject}}" content-template="idOfTemplateInTemplateCache" hoverablePopover="">
You have to put the trigger in single quotes, because, reasons:
<button uib-popover="I appeared on mouse enter!" popover-trigger="'mouseenter'" type="button" class="btn btn-default">Mouseenter</button>
demo:
https://jsbin.com/fuwarekeza/1/edit?html,output
directive:
myAppModule.directive('popoverHoverable', ['$timeout', '$document', function ($timeout, $document) {
return {
restrict: 'A',
scope: {
popoverHoverable: '=',
popoverIsOpen: '='
},
link: function(scope, element, attrs) {
scope.insidePopover = false;
scope.$watch('insidePopover', function (insidePopover) {
togglePopover(insidePopover);
})
scope.$watch('popoverIsOpen', function (popoverIsOpen) {
scope.insidePopover = popoverIsOpen;
})
function togglePopover (isInsidePopover) {
$timeout.cancel(togglePopover.$timer);
togglePopover.$timer = $timeout(function () {
if (isInsidePopover) {
showPopover();
} else {
hidePopover();
}
}, 100)
}
function showPopover () {
if (scope.popoverIsOpen) {
return;
}
$(element[0]).click();
}
function hidePopover () {
scope.popoverIsOpen = false;
}
$(document).bind('mouseover', function (e) {
var target = e.target;
if (inside(target)) {
scope.insidePopover = true;
scope.$digest();
}
})
$(document).bind('mouseout', function (e) {
var target = e.target;
if (inside(target)) {
scope.insidePopover = false;
scope.$digest();
}
})
scope.$on('$destroy', function () {
$(document).unbind('mouseenter');
$(document).unbind('mouseout');
})
function inside (target) {
return insideTrigger(target) || insidePopover(target);
}
function insideTrigger (target) {
return element[0].contains(target);
}
function insidePopover (target) {
var isIn = false;
var popovers = $('.popover-inner');
for (var i = 0, len = popovers.length; i < len; i++) {
if (popovers[i].contains(target)) {
isIn = true;
break;
}
}
return isIn;
}
}
}
}]);
html:
<span class="icon-globe visibility"
id="visibilityFor{{post.metaData.assetId}}"
popover="{{post.visibilityListStr}}"
popover-is-open="{{post.$open}}"
popover-trigger="click"
popover-hoverable="true"
visibility>
</span>
html
<span class="icon-globe" id="visibilityFor" popover="hello how are you"
popover-placement="right" popover-trigger="mouseenter"
popover-popup-delay="50" viz>
</span>
directive
myAppModule.directive('viz', function ($rootScope,$timeout){
return{
restrict:"A",
link: function (scope, element, attrs) {
$rootScope.insidePopover = false;
element.bind('mouseenter', function (e) {
$timeout(function () {
if (!$rootScope.insidePopover) {
element.popover('show');
// attachEvents(element);
}
}, 200);
});
element.bind('mouseout', function (e) {
$timeout(function () {
if (!$rootScope.insidePopover) {
element.popover('show');
// attachEvents(element);
}
}, 200);
});
}
}
});
Note : - Don't forget to include angular-strap after jQuery.js & angular.js
This feature was added in Angular UI Bootstrap 0.14.0 and is documented here. Disable the triggers and use the popover-is-open property to manually dictate the opened/closed state.
What I did that gets my by in 0.13.X is to set the element to be hoverable to a <button> and then set the popover-trigger="focus". Then style the button how you wish, and focus the button by clicking it. You can hover in the popover and click a link, all I need to do.
Easiest way to have a mouse-event using uib-popover
Look at the below working example !
You need not have a uib-tabset, I faced an issue with uib-tabset and so added that example.
<uib-tabset>
<uib-tab>
<uib-tab-heading>
Tab 1
</uib-tab-heading>
<div>
<span ng-mouseover="popoverIsOpen = true"
ng-mouseleave="popoverIsOpen = false">
<button uib-popover-template="'includeFile.html'"
popover-trigger="outsideClick"
popover-is-open="popoverIsOpen"
popover-placement="right"
type="button" class="btn btn-default">
Dynamic Popover
</button>
</span>
</div>
<p> tab 1</p>
</uib-tab>
<uib-tab>
<uib-tab-heading>
Tab 2
</uib-tab-heading>
<p> tab 2</p>
</uib-tab>
</uib-tabset>
Template: includeFile.html
<div>
<span>This is for tesitng</span>
<strong> www.google.com</strong>
</div>
I needed to do this as well. I have a checkbox in a table cell that can have 3 possible states: Enabled, Disabled, or Special case. The UI spec I have asked for a popover over the box that shows either of those statuses, or for the special case a sentence with a link.
I tried several of these solutions and one of them worked for me, and they all added extra code. After some playing around, I determined I could just add the "popover-popup-close-delay" attribute with a dynamic value. So this works for me:
<td uib-popover-html="getPopoverTxt()" popover-popup-close-delay="{{ele.isspecial ? 2000 : 300}}" popover-popup-delay="300" popover-append-to-body="true" popover-placement="top" popover-trigger="mouseenter">
<input id="issynced{{ele.id}}" name="isChecked" type="checkbox" data-ng-checked="ele.ischecked" data-ng-model="ele.ischecked">
<label for="issynced{{ele.id}}"></label>
</td>
Some context: My table is looping over an array of data objects, so ele is a single object. The getPopoverTxt() is just a simple method in my controller that returns one of the 3 labels I want to show ("Enabled", "Disabled", or "Special Text with HTML"). Its not necessary here, but the takeaway is to get the HTML to work, you have to wrap the string value in $sce.trustAsHtml(), like:
var specialText = $sce.trustAsHtml('Text with a link to contact support');
The rest is all the usual popover and form input settings we normally use. The "popover-popup-close-delay" is the key.

Resources