How do I create an AngularJS tooltip directive with a compiled template? - angularjs

I've searched the internet long and hard and found no straight answer. My question is simple, I want to have something like this in my markup:
<div my-tooltip-template="'mytt.tpl.html'" my-tooltip-scope="myDataItem">Some text...</div>
EDIT: Where myDataItem is a scope variable which contains my data object, and with a template which might look like:
<h1>{{dataItem.title}}</h1>
<span>{{dataItem.description}}</span>
And I want to have that template compiled with the a scope which contains myDataItem as dataItem and displayed as a tooltip. As far as I could tell by experimenting with ui-bootstrap tooltip module, the way to inject html into a tooltip is by using the directive tooltip-html-unsafe but the html injected doesn't get compiled, i.e., angular expressions are not evaluated, directives are not expanded, etc.
How do I go about creating a directive for this? I want a lean result, I don't want to have to include jQuery or any other library, just AngularJS and ui-bootstrap.
Thanks a lot!

Here're the blueprints of how you could create a tooltip according to your requirements (or modify/encorporate this with ui-bootstrap's tooltip).
app.directive("myTooltipTemplate", function($compile){
var contentContainer;
return {
restrict: "A",
scope: {
myTooltipScope: "="
},
link: function(scope, element, attrs, ctrl, linker){
var templateUrl = attrs.myTooltipTemplate;
element.append("<div ng-include='\"" + templateUrl + "\"'></div>");
var toolTipScope = scope.$new(true);
angular.extend(toolTipScope, scope.myTooltipScope);
$compile(element.contents())(toolTipScope);
}
};
});
This, of course, doesn't have any of the actual tooltip functionality, like popup, placement, etc... - it just appends the compiled template to whatever the element that this directive applies to.
Plunker
Changed plunker with closer-to-tooltip behavior;

I was able to just copy and override the directive for the tooltip-html-unsafe
angular.module( 'ui.bootstrap.tooltip')
.directive( 'tooltipSpecialPopup', function () {
return {
restrict: 'EA',
replace: true,
scope: { content: '#', placement: '#', animation: '&', isOpen: '&' },
templateUrl: 'tooltip.tpl.html'
};
})
.directive( 'tooltipSpecial', [ '$tooltip', function ( $tooltip ) {
return $tooltip( 'tooltipSpecial', 'tooltip', 'mouseenter' );
}]);
I just changed the unsafe bit to special in the directive name and changed the template.
Here's a Plunker

I found this question searching for a classic tooltip implementation in AngularJS, jQuery (and other libraries) free.
I have built the following directive if anyone is searching for one:
app.directive('tooltip', function($sce, $compile) {
return {
restrict: 'A',
scope: {
content: '=tooltipContent'
},
link: function(scope, element, attributes) {
/* Attributes */
scope.displayTooltip = false;
/* Methods */
scope.updateTooltipPosition = function(top, left) {
tooltip.css({
top: top + 'px',
left: left + 'px',
});
};
scope.getSafeContent = function(content) {
return $sce.trustAsHtml(content);
};
/* Bootstrap */
var tooltip = angular.element(
'<div ng-show="displayTooltip" class="tooltip">\
<span ng-bind-html="getSafeContent(content)"></span>\
</div>'
);
angular.element(document.querySelector('body')).append(tooltip);
/* Bindings */
element.on('mouseenter', function(event) {
scope.displayTooltip = true;
scope.$digest();
});
element.on('mousemove', function(event) {
scope.updateTooltipPosition(event.clientY + 15, event.clientX + 15);
});
element.on('mouseleave', function() {
scope.displayTooltip = false;
scope.$digest();
});
/* Compile */
$compile(tooltip)(scope);
}
};
});
You can test it here: http://jsfiddle.net/m4sr9jmn/2/
Hope it will help !

Related

How to render custom directive inside ui-bootstrap tooltip

I have following code
// Template
<span>
hello world!
</span>
// Directive
'use strict';
var referenceFieldTemplate = require('./reference-field.html');
module.exports = directiveFunction;
/* #ngInject */
function directiveFunction($http) {
return {
restrict: 'E',
scope: {
fieldName: '=',
fieldValue: '='
},
templateUrl: referenceFieldTemplate,
controller: function($scope){
console.log($scope);
},
link: function(scope, element, attr) {
element.bind('mouseover',function(e) {
console.log(e);
});
}
}
}
I'm trying to render custom directive in uib-tooltip using following code
function eventRender(event, element, view) {
var hoverMarkup = '\'<reference-field field-name="test" field-value="test"></reference-field>\''
element.attr({
'uib-tooltip-html': hoverMarkup,
'tooltip-append-to-body': true,
'tooltip-class': 'tooltip-wrapper'
});
$compile(element)($scope);
};
After compilation, the directive is not there in DOM. I want to understand the reason why it's not getting rendered. There are no errors in console.
Thanks in advance.
I've done recently something similar using "uib-tooltip-template", put all your html code inside the template and it will work just fine.
You just need to create a html file with your directive inside and asign it via $scope to the tooltip.

how to create custom directives in Angularjs that changes its state of being used in application using an id?

I am working on an application that has same directives but the content change and template depends on the id of a controller in a module.
app.directive('glTranslate', function() {
return {
restrict: 'A',
link: function(scope, element, attrs) {
var key = element.text();
attrs.$observe('options', function(value) {
var options;
try {
options= JSON.parse(value);
element.text( I18n.t( key, options ) );
} catch (err) {
// SyntaxError maybe thrown as the options string is dynamically updated through Angular bindings
// and the options string may be momentarily be syntactically incorrect
console.log("Error parsing options: " + value + "\nError:"+ err.message + "\n");
}
});
}
};
});
If I understand you correctly, to set a directive's template dynamically based on some condition, you can use ng-include inside the directive. The pseudocode below works. It includes an example directive with dynamic template chosen based attribute passed to it and simple HTML usage. Needless to say, although the template contents vary, here they use the same directive controller and its accompanying functionality. Hope this helps.
.directive("dynaDirective", function() {
return {
template: '<ng-include src="getTemplateUrl()"/>',
scope: {
someData: '='
},
restrict: 'E',
controller: function($scope) {
//function used on the ng-include to resolve the template
$scope.getTemplateUrl = function() {
//basic handling
return "templateName" + $scope.someData.type;
}
$scope.doSomething1 = {
//......
}
$scope.doSomething2 = {
//......
}
}
};
});
<dyna-directive some-data="someObject"></dyna-directive>

Directive referencing its own id

I've used a directive to utilize jqueryUI dialogs.
app.directive('popUp', function() {
return {
restrict: 'E',
scope: {
myId: '#',
onCancel: '&'
},
template:
'<div id="{{myId}}">
<button ng-click="onCancel()">...</button>
...
</div>'
link: function(scope, element, attrs) {
scope.closeDialog = function() {
$("#" + id).dialog('close');
}
// question 1: how to reference id of this directive (self)?
// question 2: should it be here, in compile, or in directive controller?
// question 3: 'ng-click=closeDialog()' missing when popup element inspected in firebug/dev tool
// question 4: is there a way to avoid jquery like selector $("#" + id) to reference this element?
}
};
});
And this is the html:
<pop-up my-id="success" on-cancel="closeDialog()"> ... </pop-up>
If I declare an external controller and closeDialog function attached to its $scope, this works fine, like this:
app.controller('DialogCtrl', function($scope) {
$scope.closeDialog = function(id) {
$("#" + id).dialog('close');
};
});
html
<div ng-controller="DialogCtrl">
<pop-up my-id="success" on-cancel="closeDialog('success')"> ... </pop-up>
</div>
But what I want to avoid is redundancy of the id. So I want the directive to have its own close function. If you also have answers on the other questions above, it is very much appreciated. Thanks.
This is essentially what you want. There's no need to use $ selectors and ids to find your dialog since element in the link function will give you a reference to the element that the directive is applied to.
Define the closeDialog function on the directive's scope and you can reference it from the directive's template. Each instance of the directive will have its own close function.
app.directive('popUp', function() {
return {
restrict: 'E',
scope: {
myId: '#'
},
template:
'<div id="{{myId}}">
<button ng-click="closeDialog()">...</button>
...
</div>'
link: function(scope, element, attrs) {
// initialise dialog
element.dialog();
// the template's ng-click will call this
scope.closeDialog = function() {
element.dialog('close');
};
}
};
});
No need for an on-cancel attribute now.
<pop-up my-id="success"></pop-up>

Click event and two way data binding in HighCharts using AngularJS

I'm trying to write a directive for HighCharts in AngularJS which supports two way data binding as well as click events on charts.
Directive:
app.directive('highchart', function () {
return {
restrict: 'E',
template: '<div></div>',
replace: true,
link: function (scope, element, attrs) {
scope.$watch(scope.example_chart, function() {
var chart = JSON.parse(attrs.chart)
element.highcharts(chart);
}
}
}
});
Now, when I write my HTML like this:
<div>
<highchart chart='example_chart'></highchart>
</div>
It supports the click event, but not two way data binding.
And, when it is passed as an expression:
<div>
<highchart chart='{{example_chart}}'></highchart>
</div>
It supports two way data binding but the function written in JSON of example_chart for click event doesn't get parsed and hence not functioning.
So, suggest a way to handle both the cases in AngularJS way.
highcharts-ng
You can use highcharts-ng directive, See usage here: Fiddle
Also you can use custom directive:
Custom
See demo in Fiddle
Actually there is nothing special here, pretty simple isolate scope directive with watcher on highChart configuration (defined as JSON).
I my case I used several watchers on specific fields to improve perforamnce but you can run deep watch on all config object
HTML
<high-chart config="chartConfig"> </high-chart>
JS
myapp.directive('highChart',
function () {
return {
restrict: 'E',
replace:true,
scope: {
config: '='
},
template: '<div id="container" style="min-width: 310px; height: 400px; margin: 0 auto"></div>',
link: function (scope, element, attrs) {
var chart = false;
var initChart = function() {
if (chart) chart.destroy();
var config = scope.config || {};
//var mergedOptions = getMergedOptions(scope, element, config);
chart = new Highcharts.Chart(config);
if(config.loading) {
chart.showLoading();
}
};
initChart();
scope.$watch('config.loadRandomData', function (value) {
if(value == false){return;}
chart.series[0].setData(scope.config.series[0].data);
scope.config.loadRandomData = false;
}, true);
scope.$watch('config.loading', function (loading) {
if(loading) {
chart.showLoading();
} else {
chart.hideLoading();
}
});
scope.$watch('config.series[0].type', function (type) {
chart.series[0].update({type: type});
});
scope.$watch('config.series[0].dataLabels.enabled', function (enableDataLabels) {
chart.series[0].update({dataLabels: {enabled: enableDataLabels}});
});
}//end watch
}
}) ;

Class directive not working when using ng-class to load the directive

I am trying to load a 'class' directive using ng-class. but my directive is never loaded when i do that. The directive is a multipurpose directive, and I don't want to create an isolated scope on this. it will be loaded only when required, based on ng-class conditions hence not using attribute or element directive. has anyone tried doing this and succeeded?
this directive is called using <div ng-class="someClass {{tooltip: enabled}}"></div>
here enabled is a scope variable.
app.directive('tooltip', ['$timeout', '$location', '$rootScope', function (timer, $location, $rootScope) {
return {
restrict: 'C',
transclude: true,
link: function (scope, element) {
var printContent = function () {
/* uses the content of .tooltip-content if it is a complex html tooltip,
otherwise
you can use the title attribute for plaintext tooltips
*/
var tooltipContent = $(element).find('.tooltip-content').html();
if (!tooltipContent) {
tooltipContent = $(element).attr('title');
}
$(element).tooltip({
content: tooltipContent,
items: "img, a, span, button, div",
tooltipClass: "tooltip",
position: { my: "left+30 top", at: "right top", collision: "flipfit" },
show: { effect: "fadeIn", duration: "fast" },
hide: { effect: "fadeOut", duration: "fast" },
open: function (event, ui) { $rootScope.tooltipElement = event.target; }
});
};
timer(printContent, 0);
}
};
}]);
Interesting issue. It seems that you don't want to use the ng-class directive since that doesn't recompile the content after adding the class. You'll likely want to create your own dynamic-class directive that actually recompiles when the value is true:
app.directive('dynamicClass', function($compile) {
return {
scope: {
dynamicClassWhen: '=',
dynamicClass: '='
},
link: function(scope, elt, attrs) {
scope.$watch('dynamicClassWhen', function(val) {
if (val) {
console.log(val);
elt.addClass(scope.dynamicClass);
$compile(elt)(scope);
}
});
}
};
});
You may need to modify this for the ability to remove the class and depending on if the $compile is sufficient for you or if you need to further manipulate the html, but this seems to be the right track for you. I made a fiddle with this in action.
Hope this helps!

Resources