AngularJS Databinding Expression not working inside tooltip plugin template - angularjs

*EDIT: Mike pointed out an issue with a type. the real problem i want to solve includes a template with cluetip. See this revised plnkr:
http://plnkr.co/edit/UGH3cV3z9MrqA4eyPjLc?p=preview
I'm sure this is related to the digest loop and the jquery plugin cluetip, but I don't know what steps I need to make the data binding work inside template. I've put the simple example in plnkr to show what I mean.
http://plnkr.co/edit/YW7AsTEuJh2ixqSUJpld?p=preview
The code in question is this:
head>
Cluetip - AngularJS
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.26/angular.min.js"></script>
<link rel="stylesheet" href="jquery.cluetip.css" type="text/css" />
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script src="jquery.cluetip.js"></script>
<script type="application/javascript">
$(function() {
$('a.title').cluetip({
splitTitle: '|'
});
});
</script>
</head>
<body ng-app>
<input ng-model="somedata" placeholder="Some Data">
<br/>{{ somedata }}
<hr/>
<br/>
<a class="title" href="#" title="This is the title| someData: {{ someData }} .|In this case, the delimiter is a pipe">In Line Text</a>
</body>

Couple of issues going on here...
First, you don't have a controller managing this, so the scope that is created by the tag is not visible to the somedata reference in your tooltip title. To correct this, you need to reference a controller:
<body ng-controller="MainCtrl">
and setup the somedata scope value in that controller:
$scope.somedata = 'somedata';
Second, you have a small typo in the title reference (you have a capital "D" in somedata):
title="This is the title| someData: {{ someData }} .|In this case, the delimiter is a pipe"
should be
title="This is the title| someData: {{ somedata }} .|In this case, the delimiter is a pipe"
And, finally, it appears the jQuery cluetip code is creating a copy of the value, so it's not dynamic. In reality, it's probably setting up the DOM objects once at initialization and never referencing the "title" attribute again -- just hiding and showing the created content. Therefore, changing the value of the "title" attribute appears to be ignored.
I forked a Plnkr here with the above changes (including referencing the script.js file where a controller now resides): http://plnkr.co/edit/hzW6AtJBj4zPPM405n5Y?p=preview
Notice it all works; however, the cluetip doesn't change dynamically as the somedata value changes. I made a duplicate of the anchor below the first one in the Plnkr, but changed the class so cluetip wouldn't attach and it's a standard tooltip. You'll see that this tooltip does update dynamically -- using the same input box and somedata.
Beyond the above, I think you'll have to find a way to either trigger and update to the cluetip initialization or use a different widget. As an aside to all this, you'd probably be better served exploring a native angular directive for this so you don't run into this type of issue. Maybe something like http://angular-ui.github.io/bootstrap/#/tooltip

Related

uib-popover-html won't accept my html string

I use the verions 0.14.2 of angular-ui-bootstrap. I was unable to display line returns in the popover.
I use the popover-html directive, and a string such as
Limite inférieure<br>Limite supérieure
It gives the following error :
Lexer Error: Unexpected next character at columns 41-41 [é] in expression [<div>Approchant des limites<br>Limite supérieure: 34:12<br>Limite inférieure: -34:12</div>].
I tried wrapping my string in a $sce.trustAsHtml call, but it didn't change a thing.
Here is a plunker
http://plnkr.co/edit/3JSly1anPBUiGyqBcsD1
Works for me using $sce.trustAsHtml as below.
Note: trustAsHtml tells Angular to trust that the HTML is safe, so should only be used if you do trust the HTML, i.e. its not user-supplied.
JS:
$scope.popoverContent = $sce.trustAsHtml('Line 1<br>Line2');
HTML:
<button popover-placement="right" uib-popover-html="popoverContent" type="button" class="btn btn-default">Popover</button>
Updated Plunker
Or if your content is dynamic and you need a function:
JS:
$scope.input = 'Line 1<br/>Line 2';
var trusted = {};
$scope.getPopoverContent = function(content) {
return trusted[content] || (trusted[content] = $sce.trustAsHtml(content));
}
HTML:
<button popover-placement="right" uib-popover-html="getPopoverContent(input)" type="button" class="btn btn-default">Popover</button>
Plunker
(The reason for caching the value returned by trustAsHtml is that trustAsHtml always returns a new object so can cause an infinite $digest loop)
The accepted approach can easily lead to a cross-site scripting vulnerability in you application. You should really only use $sce.trustAsHtml if you explicitly trust the content that you want to display. The angular-bootstrap documentation also hints at that:
The user is responsible for ensuring the content is safe to put into the DOM!
An alternative and safer approach is to use uib-popover-template with a simple template in combination with ng-bind-html that automatically uses $sanitize to sanitize the HTML.
HTML
<p uib-popover-template="myPopoverTemplateUrl"
popover-trigger="mouseenter"
popover-placement="top"
popover-append-to-body="true">
Show a Popover on Hover
</p>
<script type="text/ng-template" id="myPopoverTemplate.html">
<div>
<p ng-bind-html="popoverContent"></p>
</div>
</script>
JS
$scope.myPopoverTemplateUrl = "myPopoverTemplate.html";
$scope.popoverContent = "This is HTML <b> And will be sanitized."
You also need to make sure to declare ngSanitize in your app and to include the angular-sanitize.js script. Please take a look at the updated plunker for reference.
Updated Plunker

ng-model with dynamic form inputs

I'm having trouble dynamically capturing form inputs in angular. I have a form that takes several inputs for a resource. Each resource has many sections, and each section has many links. I've been able to get the functionality to work for a user to dynamically add/remove sections and links, but when it comes to actually capturing that with ng-model I can't seem to get it.
Based on this stackoverflow post, I thought I could do something like the first answer, ng-model="newResourceForm.sections[section.title]", but that doesn't seem to be working for me (it says that it is undefined)
Here is a link to a plunkr that I made for it:
Looks like the problem in your code is that you are binding your variables to
newResourceForm.sections
but you are creating new sections inside an array named sections without a title property.
Using ng-model="newResourceForm.sections[section.title]" works but section.title is undefined. The result is your newResourceForm.sections object contains just one section named undefined no matter how many objects you have in your sections array.
The way you're adding/editing sections is a little off. It's hard to say without looking at your controller code, but I think this is part of the issue:
<div ng-repeat="section in sections" class="form-group mt">
and it should look more like
<div ng-repeat="section in resource.sections" class="form-group mt">
I made a really minimal working version of what I think you were going for, feel free to try it out! (you just have to change the location of angular.min.js)
<html>
<head>
<script src="static/angular.min.js"></script>
<script>
angular.module('app', [])
.controller('resourseCtrl', resourseCtrl);
function resourseCtrl() {
this.resource = {'sections': []};
console.log('controller started');
this.addSection = function() {
this.resource.sections.push({});
};
this.removeSection = function() {
this.resource.sections.splice(this.resource.sections.length - 1, 1);
};
}
</script>
</head>
<body ng-app="app">
<div ng-controller="resourseCtrl as resourseCtrl">
<button ng-click="resourseCtrl.addSection()">add section</button>
<button ng-click="resourseCtrl.removeSection()">remove section</button>
<div ng-repeat="section in resourseCtrl.resource.sections">
<p>name:<input text ng-model="section.name"></input></p>
<p>title:<input text ng-model="section.title"></input></p>
<p>description:<input text ng-model="section.description"></input></p>
</div>
{{ resourseCtrl.resource }}
</div>
</body>
</html>

Simple practical example for two way data binding in AngularJS

Can any one help me out to understand what exactly two way data binding in AngularJS means with a help of simple code.
One way data binding -
The model values are automatically assigned to the HTML placeholder elements specified through the data binding notation, but the HTML elements don't change the values in the model(one way).
Example :
Controller :
app.controller('MainCtrl', function($scope) {
$scope.firstName = 'John';
});
HTML :
<span>First name:</span> {{firstName}}<br />
Two Way Data Binding -
The model values are automatically assigned to the HTML placeholder elements specified through the data binding notation, where HTML elements can change the value in the model(two way).
Example :
Controller :
app.controller('MainCtrl', function($scope) {
$scope.firstName = 'John';
});
HTML
<span>First name:</span> {{firstName}}<br />
<span>Set the first name: <input type="text" ng-model="firstName"/></span><br />
In above example we can change firstName model value with the help of HTML Input element.
Working example : http://plnkr.co/edit/GxqBiOoNFuECn55R4uJZ?p=preview
Retrieved from the AngularJS homepage (2015.06.02):
<!doctype html>
<html ng-app>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js"></script>
</head>
<body>
<div>
<label>Name:</label>
<input type="text" ng-model="yourName" placeholder="Enter a name here">
<hr>
<h1>Hello {{yourName}}!</h1>
</div>
</body>
</html>
This is possibly the simplest example of two-way data binding in Angular.
The <input> is associated to a yourName model, and the same model is used to fill the content of the <h1> tag. Modifying one will automatically update the other.
Although the data binding in the example can be seen as one-way, because you can't modify the <h1> directly, this should get you started. The AngularJS docs and tutorials contain a lot of great resources.

Angular Bootstrap Tabs Set Active Tab Through Markup

I'm trying to set a tab as active through the markup. For some reason when I set the active attribute on a tab it seems to mangle the state of the tabs. The page loads up fine and the tab that was set as active will be deactivated when clicking another tab. When I click back on the tag that was set with active="true" the previously selected tab will not be deselected.
...
<tab heading="Dynamic Title 1" active="true">Some Title 1</tab>
...
http://plnkr.co/edit/xzDbezXgkSMr6wokov6g?p=info
I switched to creating a variable that is set to true at init and plopped that into the active attribute. I'm hoping there's a better way to this though.
<tabset ng-init="startActive = true">
...
<tab heading="Dynamic Title 1" active="startActive">Some Title 1</tab>
...
</tabset>
http://plnkr.co/edit/mt5MQSZEl730fsMuMxg8
I don't want to define the tabs in js because this is a project that uses webforms and piping data from that to js might be worse than what I'm doing here. I change the page to be completely built with angular in which case piping data like the tab to be selected could be part of some config endpoint that would be hit on the controller's init. I'd rather not have to redesign a complete page to make this change but it seems like the most correct way to go. Any thoughts and tips would be appreciated.
I know this is quite old, but after wasting hours of my life, I came up with a super dirty hack, that does the trick (assuming I understood your question correctly and you have the same issue as me).
Problem Statement
Using UI Bootstrap Tabs, dynamically adding tabs based on list data and maintaining the active state outside of this data.
When using the Tabs of UI Bootstrap and generating tabs like this:
<tab ng-repeat="item in page.data.list" active="item.active">
UI Bootstrap will use the binding of the item to store the active state. If we omit the active attribute, UI Bootstrap will maintain it internally but then it becomes impossible to manipulate the active state from the outside, except for accessing it via one of these: $$ (which are the untouchables!)
Solution (The Hack)
Maintain the active state in another list:
<div ng-controller="parasample-tabs">
{{activeState}}
<tabset ng-show="page.data.list.length">
<tab ng-repeat="item in page.data.list" active="activeState[$index]">
<tab-heading>
<i style="cursor: pointer" class="glyphicon glyphicon-remove" ng-click="delTab($index)" prevent-default></i>
Item {{$index +1}}
</tab-heading>
{{item.text}} - {{item.transcript}} - {{item.active}}
</tab>
</tabset>
<!--
For me this problem arose because I like to use self-contained, self-managing data
from factories, hence I call addItem not on a controller
-->
<button ng-click="page.addItem()">Add Item</button>
</div>
Now for the controller, that is wrapped around that tabs and manages them, and their active state instead of writing it into my data:
app.controller('parasample-tabs', function ($scope) {
$scope.maxItems = 5;
$scope.activeState = [];
$scope.delTab = function (idx) {
var list = $scope.page.data.list;
if (list.length > 0) {
$scope.page.delItem(idx);
$scope.activeState.splice(idx, 1);
}
};
$scope.$watch(
"page.data.list.length",
function (newLen, oldLen) {
if (!newLen) return;
// new item => new tab, make active
if (newLen > oldLen)
$scope.activeState.push("true");
}
);
});
Now UI Bootstrap will access the array activeState and store the active state there. There is no need for initialisation, as that is taken care of.
When a new item is added to our data list, the watch will set the new tab as the active tab (thats my preference) and the rest of the list will be updated by UI Bootstrap.
When deleting however, it is not easily possible to determine which item was removed, so I had to wrap my page.delItem into the controller's delTab method.
Here is a fiddle to play with, too.
Let's hope that UI Bootstrap will allow for a different way to maintain the active state instead of having a two way binding in the active attribute. I like having an "active ID" variable.
Disclaimer: I am aware of how dirty this is and I only tested it in Chrome so far and it works nicely.
You're missing quite a few here. Here's a more extensible way:
var app = angular.module('myApp',[]);
app.controller('MyController', ['$scope',function($scope){
$scope.tab = 0;
$scope.changeTab = function(newTab){
$scope.tab = newTab;
};
$scope.isActiveTab = function(tab){
return $scope.tab === tab;
};
}]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<!DOCTYPE HTML>
<html ng-app="myApp">
<head>
<style type="text/css">
.active{
background-color:red;
}
</style>
</head>
<body ng-controller="MyController">
<div>
<div ng-class="{'active':isActiveTab(0)}" ng-click="changeTab(0)">Some Title 1</div>
<div ng-class="{'active':isActiveTab(1)}" ng-click="changeTab(1)">Some Title 2</div>
</div>
<br/>
<div ng-show="isActiveTab(0)">tab1</div>
<div ng-show="isActiveTab(1)">tab2</div>
<script type="text/javascript" src="js/angular-1.2.24.min.js"></script>
<script type="text/javascript" src="js/app.js"></script>
</body>
</html>
Initialization should always be in the controller.
Change the values using a controller function. Here, defined as 'changeTab()'
For checking active tabs, create a controller function to compare if the current value of $scope.tab is equal to the current tab.
I also added a bit of styling to impose which tab is active.

What's the best way for an angular controller to alter an ancestor HTML element?

I have an Angular controller which lives in a portion of the page however that application needs to get access to an HTML element that lives above it.
The best way to imagine the problem is that you have an embedded video which wants to request to be made full view-port:
<html ng-app="videoApp">
<body>
<div>
Other stuff that doesn't relate to the video player...
</div>
<div ng-controller="videoCtrl">
... stuff relating to the video controller
</div>
</body>
</html>
The exact problem is that the videoCtrl needs to be able to add a class to the body class such that it can switch the page layout to being full-page and dominated by the video.
Desired outcome, status of video app adds a "full-page" class to body tag:
<body ng-class="video.fullPage ? 'full-page', : ''">
What's would be the correct way for the video to add a class to an ancestor tag?
Under normal circumstances where the element we want to manipulate lies inside the controller that's doing the manipulation we can bind elements to variables in the scope:
<body> <!-- the videoCtrl scope is not available to <body> -->
<div ng-controller="videoCtrl">
<div ng-class="video.fullPage ? 'fullPage' : ''"></div>
</div>
</body>
However the body tag is not contained within the scope of the video controller and so has no access to the variables in its scope so we can't bind to them.
We could always reach out directly and change the class on the body using dom manipulation but that's not very Angular. What is the correct pattern for the video controller to alter the class of the body tag?
To meet a very similar requirement, I used Angular events. From the controller, $broadcast an event on $rootScope. Then have some sort of screen layout controller handle the event and toggle the possible screen configurations.
So:
child controller:
$rootScope.$broadcast('layout-action', { configuration: 'video' });
layout controller:
$scope.$on('layout-action', function(event, args) {
if (args.configuration == 'video') {
$scope.showVideo = true;
}
});
html:
<body ng-class="{'full-page': showVideo}">
*Note: this does slightly tie the child functionality to another part of the layout. However, perhaps try to generalize the video layout. For example, maybe you want a full screen mode instead.
If ng-app="videoApp" is the only parent, you could also do something on the button that would trigger the full-page class like this:
<body ng-class="{'full-page': showVideo}">
<div ng-controller="videoCtrl">
<button ng-click="$parent.showVideo = !$parent.showVideo">I'm in the video controller!</button>
</div>
</body>
In this case i'm just toggling showVideo to true or false. But like Davin said, this may not be as nice because it depends more on where it sits in the app.
See this plunkr for an example http://plnkr.co/edit/UxtA0YvCUckofAflsy9G

Resources