Angular-ui + D3: how to implement contextual menu (popover vs modal)? - angularjs

Given the following use-case:
I use D3js to render objects which are managed by AngularJS. I would like to add interactivity to the D3 chart. When clicking on a svg element I would like to have a kind of popup menu allowing to modify the object properties. These properties are required by AngularJS but are not rendered by D3.
The D3-Angular integration is derived from http://bl.ocks.org/biovisualize/5372077 which uses a closure.
Current implementation:
As of today I am using the $modal service from angular-ui bootstrap to create the popup menu. From a functionnality point of view it works pretty well:
When clicking on a svg element, D3 dispatches an event
That event is catched by Angular which calls the $modal service
Within the modal window I modify the object properties
However I am not satisfied with the rendering. I would like the popup menu to look like a popover. It should be placed close to the svg element which was clicked.
As far as I understand, I have two options:
Continue to use the $modal service and modify its look. What approach should be taken? Using the windowClass option?
Stop using the $modal service and start hacking on the popover directive. The problem is that I do not think it is possible
to add such a directive to an svg element. The solution would be to
create a popover service close to the $modal service.
Which option should be chosen? and how to implement it?
EDIT:
Working plunker using a custom my-popover directive:
http://plnkr.co/edit/5KYvxi?p=preview

It is possible to add a directives to code generated by d3, only thing you need to ensure is that you call the $compile service on the content after it has been rendered.
For the given example, it would look something like this:
.directive('barChart', function($compile){ // inject $compile
var chart = d3.custom.barChart();
return {
restrict: 'E',
replace: true,
template: '<div class="chart"></div>',
scope:{
height: '=height',
data: '=data',
hovered: '&hovered'
},
link: function(scope, element, attrs) {
var chartEl = d3.select(element[0]);
chart.on('customHover', function(d, i){
scope.hovered({args:d});
});
scope.$watch('data', function (newVal, oldVal) {
chartEl.datum(newVal).call(chart);
$compile(element.contents())(scope); // <-- call to $compile
});
scope.$watch('height', function(d, i){
chartEl.call(chart.height(scope.height));
$compile(element.contents())(scope); // <-- call to $compile
})
}
}
And in the d3's drawing function:
bars.enter().append('rect')
.classed('bar', true)
.attr('myPopover', 'Text to show') // <-- Adding an attribute here.
.attr({x: chartW,
width: barW,
y: function(d, i) { return y1(d); },
height: function(d, i) { return chartH - y1(d); }
})
.on('mouseover', dispatch.customHover);
Demo

Related

Render from angularjs directive via eventRender Fullcalendar

Is there a way to dynamically Render a template from angularjs directive via eventRender Fullcalendar ?
What I want to achieve is something like below:
eventRender: function(event, element, view) {
var template = '<my-directive></my-directive>';
element.find('.fc-event').append(template);
scope.$apply();
}
Following Documentation
The eventRender callback function can modify element.
For example, it can change its appearance via jQuery’s .css().
On eventRender insert the css classes with
eventRender: function (event, element) {
element.addClass(event.class)
}
Documentation on this topic is not broad in the subject.
It doesn't give any example of changing the template via Angularjs directives
and doesn't tell if there is any limitaion for template
to keep it treated by FullCalendar as event.
MyCodePen
EDITED_Code_Pen
1- add $compile to your controller
2- in eventRender make as bellow :
eventRender: function(event, element, view) {
var compiled = $compile('<div your-directive></div>')($scope);
element.find('.fc-content').replaceWith(compiled);
},
workingCodePen

How to hook to "OnOpen" listener for uib-popover?

Context: I am using angular 1 and this UIB Popover control.
Since there is a text field in the popover template I called, my target is to focus on that text field whenever the popover is opened.
Unfortunately, there is no popover listener/event for "onOpen".
So I tried to do a
scope.$watch(()=>{return scope.isOpen}, (obj) ={
// where scope.isOpen is the local var in the popover-is-open
// expecting to write some code here to manipulate the element
// to realise the focus operation
// but there is no popover element yet when this is called
})
I was just wondering what other options I might have?
Thanks
I found nothing on the documentation talked about events and found this issue on the ui-bootstrap github stating that they do not support events nor do they ever plan to implement them. https://github.com/angular-ui/bootstrap/issues/5060
If you're looking for a different option that would give you access to the events would be to implement your own popover directive that simply wraps bootstrap popovers. In theory, they can function the same as the ui-bootstrap and allows you to tap directly into the events provided by bootstrap.
HTML
<div my-popover="Hello World" popover-title="Title" popover-shown="myCallback()">...</div>
JavaScript ('my-popover.directive.js')
angular
.module('myModule')
.directive('myPopover', myPopover);
function myPopover() {
return {
scope: {
popoverTitle: '#',
popoverShown: '&'
},
restrict: 'A',
link: function(scope, elem, attr) {
$(elem).popover({
title: scope.popoverTitle,
content: attr.myPopover
});
$(elem).on('shown.bs.popover', function () {
if(scope.popoverShown && typeof scope.popoverShown === 'function'){
scope.popoverShown();
}
});
}
};
}
Similar to uib-popover, you can add support for additional configurations by adding additional scoped properties.

expand page element in a modal

I have a widget in the page that shows google chart for some data with couple of filters to filter the chart data and with a print icon to print it.
I want to add a button to open this same widget with the chart, filters and print functionality working in a modal with a larger screen view. because the widget is small in the page.
I have tried to add a button, and added a function for this button in the link function to open element.html() in a modal, the html worked but the issue is that the filters and the print are not functional .
What's wrong with element.html() ? I have tired to use $compile but it got me into many errors. what can I use?
app.directive("widget", function ($rootScope) {
return {
restrict: "EA",
scope: {
title: '=',
options: '='
},
transclude: true,
templateUrl: "widget.html",
link: function(scope, element, attrs, ctrl, transclude) {
scope.print = function() {....}
scope.filterChart = function() {....}
scope.expand = function() {
$rootScope.openModal("expand Modal", element.html(), {});
}
}
}
note that $rootScope.openModal is just a wrapper service that uses the $modal service, takes a title and a body
I think we have some issue with design.
To sort things out:
You have some logic (in your case "widget with the chart, filters and print functionality")
This logic should be implemented in directive or component (1.5+).
So directive name is widget like you did.
This directive you can implement in main page (what you did so far) or as part of modal. The modal is wrapper only for your widget. So create new emplty modal, put inside <widget title="xx" options=someOptions></widget> on you are fine
Since you have isolate scope directive I don't see any problem to make it work.

JQuery UI event callbacks in Directives

I'm building an AngularJS app and would like some pointers on the way I'm structuring my Angular code.
For this module, I'd like to be able to click a button that will add a Div to a specific "parent div" that has JQuery UI draggable and resizable interactions attached to it.
They could add more than one Div that is draggable and resizable.
Right now, I have one directive that generates the whole Div. The event callback for when resizing is stopped is placed in the Button that has the directive attached to it.
Directive code gist:
var app = angular.module('myApp', []);
app.controller('IpadCtrl', function($scope) {
$scope.someFunc = function () {
console.log('resized');
alert('resized');
};
$scope.test = 'sup';
});
app.directive('addImageCont', function () {
return {
restrict: 'A',
link: function (scope, element, attributes) {
console.log(attributes);
element.on("click", function() {
var div = $("<div />").css({
'width': '100px',
'height': '100px',
'background-color': 'purple'
});
div.draggable();
div.resizable();
div.on('resizestop', function(event, ui) {
console.log(ui);
var cBack = scope[attributes.callback];
cBack();
});
element.parent().find('.ipad').append(div);
})
}
};
});
JSFiddle that has an example:
http://jsfiddle.net/GA2M8/3/
Is this the right way to handle callback events? I'd like to be able to have a callback for when resizing begins too.
I know there's a better way to handle this.
It has been suggested to me that I break up my directives.
One directive that just creates the Div and appends it to the Dom.
Another directive that attaches the JQuery UI draggable interaction to it.
Another directive that attaches the JQuery UI resizable interaction to it.
I'm not sure how to chain directives if that's how I should proceed.

AngularJS: accessing the ngTouch service from a directive?

I really love how the new ng-click directive in Angular now automatically includes functionality for touch events. However, I am wondering if it is possible to access that touch-event service from my custom directive? I have lots of directives that require that I bind a click event to the given element, but I'm simply doing that using the typical jquery syntax (ex: element.on('click', function(){ ... })). Is there a way that I can bind an ng-click event to an element within a directive? Without having to manually put a ng-click tag on my element in the HTML of my view...?
I want to be able to harness the power of both click and touch events. I could obviously import a library (such as HammerJS or QuoJS) but I would prefer not to have to do that, especially since Angular is already doing it.
I can access the $swipe service and bind different elements to that, but is there a similar service for ngTouch?
For reference, this is an example of when I would want to do this:
mod.directive('datepicker', ['$timeout', function($timeout){
return {
link: function(scope, elem, attrs){
var picker = new DatePicker();
elem.on('click', function(e){
picker.show();
});
// I would rather do something like:
// elem.on('ngTouch', function(){ ... });
//
// or even:
// $ngTouch.bind(elem, {'click': ..., 'touch': ...});
}
}
}]);
UPDATE: As noted by below, the source code for the ng-click directive is here. Can anyone see a way to harness that code and turn it into a "bindable" service?
I don't think that's quite the right approach. I'd approach this by using a template within your directive and then using ngTouch within that.
mod.directive('datepicker', ['$timeout', function ($timeout) {
return {
template: '<div ng-touch="doSomethingUseful()"></div>',
link: function (scope, elem, attrs) {
var picker = new DatePicker();
scope.doSomethingUseful = function () {
// Your code.
}
}
}
}]);
UPDATE
Full example with additional attributes on the directive element:
http://codepen.io/ed_conolly/pen/qJDcr

Resources