Popover close button doesn't work (on SVG element) - angularjs

Short
The question basically is why doesn't this fiddle not working properly. The close button on the popover works only once.
Long
I am creating a set of SVG elements that is dynamically added (through angular directives) to the page, and want them to have Popovers with close buttons. My current approach is not only not working, but seems too messy. I am looking for a better solution.
HTML
<div id="test-popover">Click on the rectangle for the popover.
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" style="width:100%; height:200px;">
</svg>
</div>
Javascript
var $element = $(document.createElementNS("http://www.w3.org/2000/svg", 'rect'))
.attr({ x: 42, y: 50, width: 50, height:75, fill: "#0011ff", })
var $closebtn = $('<button type="button" class="close" aria-hidden="true">&times</button>')
.on('click', (function ($element) {
return function () {
console.log("close...");
$element.popover('hide');
}
})($element));
var $poptitle = $('<div>Title</div>').append($closebtn);
var popcontent = function () {
return "some content with <b>HTML</b>";
};
$element.popover({
html: true,
title: $poptitle,
content: popcontent,
container: 'body',
});
$('#test-popover svg').append($element);
fiddle: http://jsfiddle.net/k9vva
Does anyone have a better way of doing this?

I suspect is because the popover is changing the html and the event handler is getting removed or something. One way could be to include the hidepopup call within the html thats appended. There's a fiddle here http://jsfiddle.net/k9vva/1/ (there may be a better solution that doesn't use the main scope, but it may depend on how you are integrating it).
var $element = $(document.createElementNS("http://www.w3.org/2000/svg", 'rect')).attr(
{ x: 42, y: 50, width: 50, height:75, fill: "#0011ff", });
var $closebtn = $('<button type="button" class="close" aria-hidden="true" onclick="hidepop();">&times</button>');
var $poptitle = $('<div>Title</div>').append($closebtn);
var popcontent = function () {
return "some content with <b>HTML</b>";
};
$('#test-popover svg').append($element);
$element.popover({
html: true,
title: $poptitle,
content: popcontent,
container: 'body',
});
window.hidepop = function() {
$element.popover( 'hide' );
};

I actually fixed it by calling title as a function with closure.
var $poptitle = (function($closebtn){
return function(){
return $('<div>Title</div>').append($closebtn);
}
})($closebtn);
Thought i'd add it here in case anyone wondered. This solution seems a little weird though, but oh well...
(I actually added the creation of the $closebtn and the $popover under one closure, in case this gives anyone problems).

Related

Leaflet issues angular woes in showing and hiding? Want to get rid of $timeout

I have a leaflet being created with L.map('mapelement') being called. The issue is that if I click a button that "hides" the leaflet map, then click the button again to show, the leaflet map does not show up. However, when I put in a setTimeout within the link function before the map gets created and set it to 2 seconds, then the map shows every time (though I have to wait 2 seconds). Is there a better alternative to using $timeout in my custom "leaflet-map" directive to show and hide?
I created a naive example of a leaflet-map directive without seeing any of your code and am toggling the display of the map through ng-show. It works without any $timeout. It's hard to diagnose where your problems are stemming from without seeing any code or knowing how you are trying to toggle the map's display.
angular.module('demo', [])
.directive('leafletMap', function() {
return {
restrict: 'E',
scope: {
mapOptions: '&'
},
template: '<div><button ng-click="toggleShow()">Toggle Map</button><div class="demo-map" ng-show="isShown"></div></div>',
link: function(scope, elem, attrs) {
// Find element to bind to map
var mapElem = elem.children().find('div')[0],
// get map options from isolate scope
mapOptions = scope.mapOptions();
// State of hide/show
scope.isShown = true;
// Create Map.
var map = L.map(mapElem, mapOptions);
// Just taken from leaflet example
L.tileLayer('https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token=pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpandmbXliNDBjZWd2M2x6bDk3c2ZtOTkifQ._QA7i5Mpkd_m30IGElHziw', {
maxZoom: 18,
attribution: 'Map data © OpenStreetMap contributors, ' +
'CC-BY-SA, ' +
'Imagery © Mapbox',
id: 'mapbox.streets'
}).addTo(map);
// method to toggle the shown/hidden state of map
scope.toggleShow = function() {
scope.isShown = !scope.isShown;
};
// cleanup on element destroy
elem.on('$destroy', function() {
map.remove();
});
}
};
})
.controller('DemoController', function() {
this.mapOptions = {
center: [51.505, -0.09],
zoom: 13
};
});
.demo-map {
height: 500px;
}
<script src="//cdn.leafletjs.com/leaflet/v0.7.7/leaflet.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<link rel="stylesheet" href="//cdn.leafletjs.com/leaflet/v0.7.7/leaflet.css" />
<div ng-app="demo" ng-controller="DemoController as ctrl">
<leaflet-map map-options="ctrl.mapOptions"></leaflet-map>
</div>
Would CSS help you ?
Create one map in a visible div
visibility: visible
Create the second map in a hidden div
visibility: hidden
Position both your div in the same position
position: absolute
When you want to toggle just change the visibility of your divs
Example: http://plnkr.co/edit/voTjyMLKTxUC183nf8ML?p=preview
(Sorry it's not angular)

Dynamically added directive is not interpolated

I have a call stack like below and problem is that directive template is not interpolated. So as a result of this I can see {{ data | json }} as a string and ng-repeat is not triggered. How to approach this?
Context of situation is that I have a Highchart's chart where I need to provide clickable plot lines. On line click I need to display popover with dynamic content.
Optional question to answer:
My play with events is working well but I'm not sure if it's also well done. I would welcome any criticism on that. Idea is to hide popover on all following clicks.
Code:
1.
series: {events: {click: function(e) {drillDownCall(e, dataGroups)}
2.
function drillDownCall (e, dataGroups) {
var elem = angular.element('#drilldown');
if (!elem[0]) {
elem = angular.element('<drilldown fancy-name="dataGroups"></drilldown>');
}
elem.css({
position: 'absolute',
top: e.pageY,
left: e.pageX,
width: '150px',
height: '250px',
zIndex: '2000',
background: 'red'
});
var body = angular.element(document).find('body').eq(0);
var scope = $rootScope.$new();
scope.dataGroups = dataGroups;
body.append($compile(elem)(scope));
}
3.
.directive('drilldown', [
'$compile',
'$window',
function (
$compile,
$window
) {
return {
restrict: 'E',
replace: true,
scope: {
data: '=fancyName'
},
template: '' +
'<div id="drilldown">{{ data | json }}' +
'<ul>' +
'<li ng-repeat="group in data">{{ group.name }}</li>' +
'</ul>' +
'</div>',
link: function (scope, element) {
var ele = $compile(element)(scope),
off;
angular.element($window).on('click', function(e) {
scope.$emit('drilldown::click');
});
off = scope.$on('drilldown::click', function() {
angular.element(ele).remove();
angular.element($window).off('click');
off();
});
}
};
}]
)
I am unable test it myself but I think I know why.
Start of everything is drillDownCall and it is called by an event that is outside of Angular.js digest cycle. So Angular.js has no idea that there is a change in scope, and doesn't run a digest cycle, causing the new directive appear as non-compiled bunch of strings. (yes even you used $compile it works like that)
In summary, if I remember correct, you need at least one digest cycle to see that directive compiled. To trigger a digest cycle, you can add
$rootScope.$apply() or $rootScope.$applyAsync() or anything equivalent to it to the end of drillDownCall event handler.
Can you please try this?

Good way to dynamically open / close a popover (or tooltip) using angular, based on expression?

I have a form that is wired into angular, using it for validation. I am able to display error messages using ng-show directives like so:
<span ng-show="t3.f.needsAttention(f.fieldName)" ng-cloak>
<span ng-show="f.fieldName.$error.required && !f.fieldName.$viewValue">
This field is required.
</span>
</span>
.. where f is the form, and t3 comes from a custom directive on the form which detects whether a submission was attempted, and contains functions for checking the validity of fields.
What I am trying to accomplish is to display validation message(s) inside a popover instead. Either bootstrap's native popover, or the popover from UI Bootstrap, I have both loaded. I may also consider AngularStrap if it is easier to do it using that lib.
What I'm struggling with right now is the nature of popovers in general -- they autodisplay based on user events like click, mouseenter, blur, etc. What I want to do is show & hide the popover(s) based on the same functions in the ng-show attributes above. So that when the expression returns false hide it, and when it returns true, show it.
I know bootstrap has the .popover('show') for this, but I'm not supposed to tell angular anything about the dom, so I'm not sure how I would get access to $(element).popover() if doing this in a custom form controller function. Am I missing something?
Update
The solution mentioned in the duplicate vote still only shows the popover on mouseenter. I want to force it to display, as if doing $('#popover_id').popover('show').
You can also build your own extended triggers. This will apply to both Tooltip and Popover.
First extend the Tooltip triggers as follows:
// define additional triggers on Tooltip and Popover
app.config(['$tooltipProvider', function($tooltipProvider){
$tooltipProvider.setTriggers({
'show': 'hide'
});
}]);
Then define the trigger on the HTML tag like this:
<div id="RegisterHelp" popover-trigger="show" popover-placement="left" popover="{{ 'Login or register here'}}">
And now you can call hide and show from JavaScript, this is a show in 3 seconds.
$("#RegisterHelp").trigger('show');
//Close the info again
$timeout(function () {
$("#RegisterHelp").trigger('hide');
}, 3000);
As it turns out, it's not very difficult to decorate either the ui-bootstrap tooltip or the popover with a custom directive. This is written in typescript, but the javascript parts of it should be obvious. This single piece of code works to decorate either a tooltip or a popover:
'use strict';
module App.Directives.TooltipToggle {
export interface DirectiveSettings {
directiveName: string;
directive: any[];
directiveConfig?: any[];
}
export function directiveSettings(tooltipOrPopover = 'tooltip'): DirectiveSettings {
var directiveName = tooltipOrPopover;
// events to handle show & hide of the tooltip or popover
var showEvent = 'show-' + directiveName;
var hideEvent = 'hide-' + directiveName;
// set up custom triggers
var directiveConfig = ['$tooltipProvider', ($tooltipProvider: ng.ui.bootstrap.ITooltipProvider): void => {
var trigger = {};
trigger[showEvent] = hideEvent;
$tooltipProvider.setTriggers(trigger);
}];
var directiveFactory = (): any[] => {
return ['$timeout', ($timeout: ng.ITimeoutService): ng.IDirective => {
var d: ng.IDirective = {
name: directiveName,
restrict: 'A',
link: (scope: ng.IScope, element: JQuery, attr: ng.IAttributes) => {
if (angular.isUndefined(attr[directiveName + 'Toggle'])) return;
// set the trigger to the custom show trigger
attr[directiveName + 'Trigger'] = showEvent;
// redraw the popover when responsive UI moves its source
var redrawPromise: ng.IPromise<void>;
$(window).on('resize', (): void => {
if (redrawPromise) $timeout.cancel(redrawPromise);
redrawPromise = $timeout((): void => {
if (!scope['tt_isOpen']) return;
element.triggerHandler(hideEvent);
element.triggerHandler(showEvent);
}, 100);
});
scope.$watch(attr[directiveName + 'Toggle'], (value: boolean): void => {
if (value && !scope['tt_isOpen']) {
// tooltip provider will call scope.$apply, so need to get out of this digest cycle first
$timeout((): void => {
element.triggerHandler(showEvent);
});
}
else if (!value && scope['tt_isOpen']) {
$timeout((): void => {
element.triggerHandler(hideEvent);
});
}
});
}
};
return d;
}];
};
var directive = directiveFactory();
var directiveSettings: DirectiveSettings = {
directiveName: directiveName,
directive: directive,
directiveConfig: directiveConfig,
};
return directiveSettings;
}
}
With this single piece of code, you can set up programmatic hide and show of either a tooltip or popover like so:
var tooltipToggle = App.Directives.TooltipToggle.directiveSettings();
var popoverToggle = App.Directives.TooltipToggle.directiveSettings('popover');
var myModule = angular.module('my-mod', ['ui.bootstrap.popover', 'ui.bootstrap.tpls'])
.directive(tooltipToggle.directiveName, tooltipToggle.directive)
.config(tooltipToggle.directiveConfig)
.directive(popoverToggle.directiveName, popoverToggle.directive)
.config(popoverToggle.directiveConfig);
Usage:
<span tooltip="This field is required."
tooltip-toggle="formName.fieldName.$error.required"
tooltip-animation="false" tooltip-placement="right"></span>
or
<span popover="This field is required."
popover-toggle="formName.fieldName.$error.required"
popover-animation="false" popover-placement="right"></span>
So we are reusing everything else that comes with the ui-bootstrap tooltip or popover, and only implementing the -toggle attribute. The decorative directive watches that attribute, and fires custom events to show or hide, which are then handled by the ui-bootstrap tooltip provider.
Update:
Since this answer seems to be helping others, here is the code written as javascript (the above typescript more or less compiles to this javascript):
'use strict';
function directiveSettings(tooltipOrPopover) {
if (typeof tooltipOrPopover === "undefined") {
tooltipOrPopover = 'tooltip';
}
var directiveName = tooltipOrPopover;
// events to handle show & hide of the tooltip or popover
var showEvent = 'show-' + directiveName;
var hideEvent = 'hide-' + directiveName;
// set up custom triggers
var directiveConfig = ['$tooltipProvider', function ($tooltipProvider) {
var trigger = {};
trigger[showEvent] = hideEvent;
$tooltipProvider.setTriggers(trigger);
}];
var directiveFactory = function() {
return ['$timeout', function($timeout) {
var d = {
name: directiveName,
restrict: 'A',
link: function(scope, element, attr) {
if (angular.isUndefined(attr[directiveName + 'Toggle']))
return;
// set the trigger to the custom show trigger
attr[directiveName + 'Trigger'] = showEvent;
// redraw the popover when responsive UI moves its source
var redrawPromise;
$(window).on('resize', function() {
if (redrawPromise) $timeout.cancel(redrawPromise);
redrawPromise = $timeout(function() {
if (!scope['tt_isOpen']) return;
element.triggerHandler(hideEvent);
element.triggerHandler(showEvent);
}, 100);
});
scope.$watch(attr[directiveName + 'Toggle'], function(value) {
if (value && !scope['tt_isOpen']) {
// tooltip provider will call scope.$apply, so need to get out of this digest cycle first
$timeout(function() {
element.triggerHandler(showEvent);
});
}
else if (!value && scope['tt_isOpen']) {
$timeout(function() {
element.triggerHandler(hideEvent);
});
}
});
}
};
return d;
}];
};
var directive = directiveFactory();
var directiveSettings = {
directiveName: directiveName,
directive: directive,
directiveConfig: directiveConfig,
};
return directiveSettings;
}
For ui.bootstrap 0.13.4 and newer:
A new parameter (popover-is-open) was introduced to control popovers in the official ui.bootstrap repo. This is how you use it in the latest version:
<a uib-popover="Hello world!" popover-is-open="isOpen" ng-click="isOpen = !isOpen">
Click me to show the popover!
</a>
For ui.bootstrap 0.13.3 and older:
I just published a small directive that adds more control over popovers on GitHub: https://github.com/Elijen/angular-popover-toggle
You can use a scope variable to show/hide the popover using popover-toggle="variable" directive like this:
<span popover="Hello world!" popover-toggle="isOpen">
Popover here
</span>
Here is a demo Plunkr: http://plnkr.co/edit/QeQqqEJAu1dCuDtSvomD?p=preview
My approach:
Track the state of the popover in the model
Change this state per element using the appropriate directives.
The idea being to leave the DOM manipulation to the directives.
I have put together a fiddle that I hope gives a better explain, but you'll find much more sophisticated solutions in UI Bootstrap which you mentioned.
jsfiddle
Markup:
<div ng-repeat="element in elements" class="element">
<!-- Only want to show a popup if the element has an error and is being hovered -->
<div class="popover" ng-show="element.hovered && element.error" ng-style>Popover</div>
<div class="popoverable" ng-mouseEnter="popoverShow(element)" ng-mouseLeave="popoverHide(element)">
{{ element.name }}
</div>
</div>
JS:
function DemoCtrl($scope)
{
$scope.elements = [
{name: 'Element1 (Error)', error: true, hovered: false},
{name: 'Element2 (no error)', error: false, hovered: false},
{name: 'Element3 (Error)', error: true, hovered: false},
{name: 'Element4 (no error)', error: false, hovered: false},
{name: 'Element5 (Error)', error: true, hovered: false},
];
$scope.popoverShow = function(element)
{
element.hovered = true;
}
$scope.popoverHide = function(element)
{
element.hovered = false
}
}
For others coming here, as of the 0.13.4 release, we have added the ability to programmatically open and close popovers via the *-is-open attribute on both tooltips and popovers in the Angular UI Bootstrap library. Thus, there is no longer any reason to have to roll your own code/solution.
From Michael Stramel's answer, but with a full angularJS solution:
// define additional triggers on Tooltip and Popover
app.config(['$tooltipProvider', function($tooltipProvider){
$tooltipProvider.setTriggers({
'show': 'hide'
});
}])
Now add this directive:
app.directive('ntTriggerIf', ['$timeout',
function ($timeout) {
/*
Intended use:
<div nt-trigger-if={ 'triggerName':{{someCodition === SomeValue}},'anotherTriggerName':{{someOtherCodition === someOtherValue}} } ></div>
*/
return {
restrict: 'A',
link: function (scope, element, attrs) {
attrs.$observe('ntTriggerIf', function (val) {
try {
var ob_options = JSON.parse(attrs.ntTriggerIf.split("'").join('"') || "");
}
catch (e) {
return
}
$timeout(function () {
for (var st_name in ob_options) {
var condition = ob_options[st_name];
if (condition) {
element.trigger(st_name);
}
}
})
})
}
}
}])
Then in your markup:
<span tooltip-trigger="show" tooltip="Login or register here" nt-trigger-if="{'show':{{ (errorConidtion) }}, 'hide':{{ !(errorConidtion) }} }"></span>

Angular JS resizable div directive

My site will have multiple sections, each of which I intend to be resizable. To accomplish this I've made a "resizable" directive, e.g.:
<div class="workspace" resize="full" ng-style="resizeStyle()">
<div class="leftcol" resize="left" ng-style="resizeStyle()">
With a directive that looks something like:
lwpApp.directive('resize', function ($window) {
return {
scope: {},
link: function (scope, element, attrs) {
scope.getWinDim = function () {
return {
'height': window.height(),
'width': window.width()
};
};
// Get window dimensions when they change and return new element dimensions
// based on attribute
scope.$watch(scope.getWinDim, function (newValue, oldValue) {
scope.resizeStyle = function () {
switch (attrs.resize) {
case 'full':
return {
'height': newValue.height,
'width': (newValue.width - dashboardwidth)
};
case 'left':
return {
'height': newValue.height,
'width': (newValue.width - dashboardwidth - rightcolwidth)
};
etc...
};
}, true);
//apply size change on window resize
window.bind('resize', function () {
scope.$apply(scope.resizeStyle);
});
}
};
});
As you can see, this only resizes each div on window resize, and each directive has an isolate scope. This works fine for what it's built for, but ultimately I would like to make a subset of the divs resizable via a draggable bar. For instance
div1 div2
----------------
| || |
| || |
| || |
| || |
----------------
draggable bar in middle
On the the draggable bar's movement (in the horizontal direction), I would need to access both div1, div2's width presumably via the scope of a parent controller(?). My questions are:
Is this the "correct" way to go about making resizable divs in angular? In particular, when the size of one div affects another?
I personally feel like the answer to (1) is "No, I am not doing it correctly because I cannot communicate between directives when each has an isolate scope." If this is true, how can I account for both window and draggable resizing between divs?
This question is old, but for anybody looking for a solution, I built a simple directive to handle this, for vertical and horizontal resizers.
Take a look at the Plunker
angular.module('mc.resizer', []).directive('resizer', function($document) {
return function($scope, $element, $attrs) {
$element.on('mousedown', function(event) {
event.preventDefault();
$document.on('mousemove', mousemove);
$document.on('mouseup', mouseup);
});
function mousemove(event) {
if ($attrs.resizer == 'vertical') {
// Handle vertical resizer
var x = event.pageX;
if ($attrs.resizerMax && x > $attrs.resizerMax) {
x = parseInt($attrs.resizerMax);
}
$element.css({
left: x + 'px'
});
$($attrs.resizerLeft).css({
width: x + 'px'
});
$($attrs.resizerRight).css({
left: (x + parseInt($attrs.resizerWidth)) + 'px'
});
} else {
// Handle horizontal resizer
var y = window.innerHeight - event.pageY;
$element.css({
bottom: y + 'px'
});
$($attrs.resizerTop).css({
bottom: (y + parseInt($attrs.resizerHeight)) + 'px'
});
$($attrs.resizerBottom).css({
height: y + 'px'
});
}
}
function mouseup() {
$document.unbind('mousemove', mousemove);
$document.unbind('mouseup', mouseup);
}
};
});
I know I'm a bit late to the party, but I found this and needed my own solution. If you're looking for a directive that works with flexbox, and doesn't use jquery. I threw one together here:
http://codepen.io/Reklino/full/raRaXq/
Just declare which directions you want the element to be resizable from, and whether or not you're using flexbox (defaults to false).
<section resizable r-directions="['right', 'bottom']" r-flex="true">
For the needs of my project i added support of minimum values, so that panels can keep some width or height - (here is the gist) -
Github
Also, i created Github repo, where i added support for panels being located right of main page axis and support of minimum/maximum values. It's in example stage now, but i'm willing to turn it into a full-weight Angular directive
This does not completely answer the question, but changing scope: true solved the isolate scope problem. In particular, in my html I have:
<div ng-controller="WorkspaceCtrl">
<div class="workspace" resize="full" ng-style="resizeStyle()">
<div class="leftcol" resize="left" ng-style="resizeStyle()">
<ul class="filelist">
<li ng-repeat="file in files" id={{file.id}} ng-bind=file.name></li>
</ul>
<div contenteditable="true" ng-model="content" resize="editor" ng-style="resizeStyle()">
Talk to me
</div>
</div>
</div>
and ng-repeat="file in files" still has access to the array $scope.files defined in the controller WorkspaceCtrl. So scope: {} cuts off the scope of the directive from the scope of the parent controller, whereas scope: true simply creates a new scope for each instance of the directive AND each instance of the directive, along with its children, retains access to the parent scope.
I have not yet implemented the draggable bar which resizes these divs, but will report back when I do so.

Using a Modal Window with KendoUI inside of AngularJS

Does anyone have any experience using KendoUI's window with AngularJS?
I'm currently using Angular-Kendo but I'm not entirely sure hot to cleanly use the window. Or if there is any other solutions for presenting a modal dialog and filling it with a form loaded via a partial I'm open to that as well.
My current code looks something like this:
HTML:
<div kendo-window id="addWindow" ng-hidden></div>
JS:
$scope.addSection = function() {
$("#addWindow").data("kendoWindow").open();
return false;
};
But I hate this, and it feels wrong for how I'm doing everything else. Any thoughts on a better way?
Check out this blog post:
http://www.kendoui.com/blogs/teamblog/posts/13-06-24/announcing-angular-kendo-ui.aspx?utm_content=bufferbbe83&utm_source=buffer&utm_medium=twitter&utm_campaign=Buffer
They rewrote Angular-Kendo and have an example of a clean way to use a window.
#anise thanks for ur information
finally i also resolve the issue.
Controller
$scope.window;
$scope.OpenWindow= function() // custom function on click
{
$scope.DlgOptions = {
width: 550,
height: 400,
visible: false,
actions: [
"Maximize",
"Close"
]
};
$scope.window.setOptions($scope.DlgOptions);
$scope.window.center(); // open dailog in center of screen
$scope.window.open();
};
View
<div kendo-window="window" k-visible="false" k-modal="true"> </div>
Check out this library
https://github.com/kjartanvalur/angular-kendo-window
var windowInstance = $kWindow.open({
options:{
modal: true,
title: "Window title",
width: 400,
},
templateUrl: 'modal1.html',
controller: 'modalController',
resolve: {
parameter1: function () {
return "Test...";
}
}
});
windowInstance.result.then(function (result) {
// Here you can get result from the window
});

Resources