AngularJS popover bind to controller variable - angularjs

I'm trying to create a popover and load content into it directly from a controller.
I can succesfully bind flag into the tooltip using a directive from this answer, but the popover keeps showing the initial value of flag, even if I change flag's value with the second button.
The point is that I wish the content of the popover to change
dinamically along with the variable in the controller.
How can I make the trick?
Here's the directive:
var app = angular.module('plunker', []);
app.directive('popover', function($compile, $timeout){
return {
restrict: 'A',
link:function(scope, el, attrs){
var content = attrs.content;
var settings = scope.$eval(attrs.popover);
var elm = angular.element('<div />');
elm.append(attrs.content);
$compile(elm)(scope);
$timeout(function() {
el.removeAttr('popover').attr('data-content',elm.html());
el.popover(settings);
});
}
}
Here comes the plunker
2ND STEP
I wish I can set the container of the popover to be the <body> using that directive, so I can make the popover width 1/3 of the page.

Regarding your first problem with updating the body value - you are not binding to the scope variable, but are reading the value assigned to the element attribute in var content = attrs.content;
Since you are already using bootstrap popover, take a look at angular-ui bootstrap, who have implemented a popover directive. They support custom templates using the popover-template attribute.

The problem is that the final html of the popover is not the one you compiled, just a copy of it.
You can instead set the content option to the compiled element itself:
// remove the attribute so the popover will look at the content option value
el.removeAttr('data-content');
settings.content = elm;
el.popover(settings);
See this plunker.

The real problem here is that the popover-template directive using a template which route is stored as a string in a $scope variable (as suggested by #kpentchev in the comments of the other answer) is available only with the angular-bootstrap version 0.13.0.
That version is not available in npm yet, so after I manually updated it using bower I was able to use correctly my custom popover.

Related

custom Directive replace element not working

I have a custom directive that generates an input with its validation errors , eventually after building the input, here is the part I'm wondering about :
var html= template.get();
element.html(html);
$compile(element)(scope);
I also added to the directive object which I think is not making difference since I don't have template in the object or should it?
replace:true
but yet the directive element DOM is still there , and the generated input is being appended as a child , can you help me out in this ?
Yes, replace is used in conjunction with template/templateUrl.
For the templates that are retrieved dynamically in link, use
var html= template.get();
element.replaceWith($compile(html)(scope));
Notice that obvious drawback in comparison with replace is that directive's attributes won't be translated to template element, this has to be done manually.
Its not working because you haven't provided a template parameter and your link function was manually adding the elements. Remove the link function and add a template parameter like so and it'll work. Ive updated your fiddle with it working too
app.directive('test',function($compile){
return {
restrict:'E',
replace:true,
template:'<input type="text" name="test">'
}
});

d3-driven directive transition doesn't work inside ng-repeat

I am trying to include my d3 code inside a directive.
However, when my directive is inside a ng-repeat, the transitions won't take place.
Here's a JSFiddle of the issue: http://jsfiddle.net/hLtweg8L/1/ : You can see that when you click on the button, the rectangles position doesn't change smoothly, and 'append' is logged to the console once again.
My directive is the following:
myMod.directive('chart',function(){
return {
restrict:'A',
scope:{
data:'=',
},
link:function(scope,elem,attrs){
a=d3.select(elem[0]);
rects=a.selectAll("rect").data(scope.data,function(d));
rects.enter().append("rect")
.attr("x",function(d,i){console.log('append');return i*50+"px"})
.attr("y",100)
.attr("width",35)
.attr("height",function(d){return d.age*10+"px"})
.attr("fill","blue")
rects.transition().duration(200)
.attr("x",function(d,i){console.log('append');return i*50+"px"})
.attr("y",100)
.attr("width",35)
.attr("height",function(d){return d.age*10+"px"})
.attr("fill","blue")
}
}
})
As far as I understand it, the problem is that the elem passed inside the link function is not the same when the ng-repeat gets updated, that's why the append gets called more than once for the same data.
My question is: How can I use d3 transitions inside ng-repeat ? (Corrected Jsfiddle would help a lot). Or why is the elem not the same between different calls ? Can I tell angular that the dom shouldn't be removed and added again ?
A couple things are needed:
If you don't want ng-repeat to create a new element, you need to use the track by option so that it knows how to identify new vs. changed items:
<div ng-repeat="set in sets track by set.group">
D3 will not automatically see that the data has changed unless your directive watches for changes.
a=d3.select(elem[0]);
scope.$watch('data', function() {
updateGraph();
});
Here is an an alternate Fiddle:
http://jsfiddle.net/63tze4Lv/1/

Angular UI Bootstrap - Collapsing tab content

Using the AngularJS UI Directives for bootstrap, is there any way to collapse the tab content using the tag?
I have several tabs/pills with content, which will start collapsed (hidden). When any of the tabs is activated, the tab content should collapse open, and stay open until a close button is clicked, which will close the collapsable section.
In the controller, I set $scope.isCollapsed to true. Each of the tabs have the ng-click which calls openCollapse(), which sets isCollapsed to false. If I add the collapse="isCollapsed" directive right to the tag, then the tabs disappear too, not just the content.
How can I fix this?
It took some figuring out, but it is possible!
The main problem I had was with scoping issues and transclusion. I hadn't run into transclusion yet (I'm fairly new to Angular), so I'm still wrapping my head around it a bit.
I tried a few different ways, and a couple others might have worked, if I understood transclusion a bit better, but in the end, this was the simplest for me, and I got it working.
So basically I had to do 4 main things to get this working.
Open up ui.bootstrap-tpls-0.11.0.js (or whichever version # you're using). Do a search for angular.module("template/tabs/tabset.html". In the <div class=\"tab-content\">\n" tag, add collapse=\"isCollapsed\".
The tag (and everything in it) gets replaced when compiled, and this is the code that it is replaced with, so this way we can directly put the collapse directive where we need to.
Also in ui.bootstrap-tpls-0.11.0.js, do a search for .directive('tabset'. Inside the link: function(scope, element, attrs) {, add: scope.isCollapsed = scope.$parent.isCollapsed'
Here we're linking the transcluded scope's isCollapsed to the isCollapsed that is being set in your initial controller (you could also just put initialize isCollapsed in the controller in the next step, instead of just linking it, but I'd already initialized it in my controller and I'd linked it trying to do another method)
Still in ui.bootstrap-tpls-0.11.0.js, do a search for .controller('TabsetController'. Inside this controller, add:
$scope.$on('openCol', function(event){
$scope.isCollapsed = false;
});
$scope.$on('closeCol', function(event){
$scope.isCollapsed = true;
});
What we're doing here is adding event listeners inside the tabset's transcluded scope. What we're going to do at the end, is create an event broadcast. I also added a .$watch(), just so I could see if it was changing:
$scope.$watch('isCollapsed', function(){
console.log("isCollapsed has changed, it is now: " + $scope.isCollapsed);
});
Lastly, in the view's controller (or whichever controller contains the .openCollapse() and .closeCollapse()), change your functions from editing this scopes isCollapsed, to:
$scope.openCollapse = function(){
$rootScope.$broadcast("openCol");
}
$scope.closeCollapse = function($event){
$rootScope.$broadcast("closeCol");
}
This will broadcast the events that are being listened for in the TabsetController. So now the proper scope of the isCollapsed is being changed, and is reflected in the code. Now watch that lovely tab-content collapsing.
Please let me know if I haven't got my terminology quite right, or if there's something inherently wrong with the way I'm doing it. Or, simply, if there are other ways. Always open to suggestions :)

Angular directive doesn't work for elements added to the DOM by jquery plugin

I'm creating a fall back image directive that looks like this http://plnkr.co/edit/wxy4Sp2K02iXoQNsvkah
angular.module('directives').directive('myDirective', function() {
return {
restrict: 'C',
link: function(scope, element, attrs) {
console.log('linking');
}
}
});
My directive doesn't work for elements that are added to the DOM by the typeahead.js plugin (https://github.com/twitter/typeahead.js).
<div class='tt-suggestion'>
<div><span class="my-directive">bla</span></div>
</div>
I guess it's because Angular is not informed about the elements that are added by jQuery and hence it doesn't invoke the directive. How do I notify Angular of these changes?
You can use the Angular compile service to do this: http://docs.angularjs.org/api/ng/service/$compile
Basicly it works like this:
document.getElementById("test").innerHTML = $compile("")($scope);
ideally you shouldnt be mixing jquery and angular because they both are based on different philosophy.
jquery-- is event driven i.e. have event listeners which cause changes to model and then the programmer has to code numerous lines to change the view i.e. changing css,text etc
angular-- woo hoo! just change the model which is binded to $scope and :) your view is automatically updated
to automatically react on changing of such events angular has a compiler which studies entire html code before the app is loaded so even if there is a template which you might use later you must enclose it in so that angular compiles this so that all the special angular directive and controller perform as expected even when you remove or add templates to the dom.
here you are using typehead.js and jquery to manually manipulate the view which is against angular philosophy because when you do such maipulation angular compiler wouldnt be aware of it as it runs only when the app is initialized. Thats why before appending you should use $compile to make the angular compiler aware of this template .
in your case i would suggest the typehead present on this url
http://angular-ui.github.io/bootstrap/

Directive working in one controller and not in another

I am trying to follow the foodMe example on AngularJS. I am on step 21. Instead of creating a ng-model, I tried to do it a bit different, created a Plunker here. My issue is it only works in Restaurants.html and not inside Menu.html. I can even see the value of {{restaurant.rating}} when I view the source , e.g.
<span stars rating="{{restaurant.rating}}" readonly="true"></span>
In the HTML view-source I can see rating="3".
Only a small change from my plunker vs my sandbox is in my sandbox I use the 'Restaurant' resource to get the individual restaurant data.
Any thoughts/ideas?
The problem is that the property restaurants is an array of objects and in the Menu.html you aren't accessing it the proper way. Change it to
Menu Rating: {{restaurants[0].rating}}
<br />
<span stars rating="{{restaurants[0].rating}}" readonly="true"></span>
and it'll work. The same doesn't happen in the Restaurants.html because you're iterating through the array by using a ng-repeat.
Check out this fork of your Plunker.
Update After talking with #parsh in the comments, I could better understand the problem with his code. The issue is that the directive doesn't get re-rendered when its scope changes. That can be fixed by using a watch:
link: function (scope, element, attrs, ctrl) {
scope.$watch('rating', function() {
scope.stars = [];
for (var i = 0; i < 5; i++) {
scope.stars.push({'fm-selected': i < scope.rating});
}
});
}
I updated the Plunker script with the changes above plus a button to simulate a scope change from outside the directive.

Resources