Due to our lack of expertise in developing with AngularJS, we've come to another roadblock in our development process.
We are developing a Angular/Web API application where our page only consists of an interactive SVG diagram that displays data when a user hovers over a particular SVG tag in an Angular directive.
There are currently two custom directives in the application.
DIRECTIVE ONE:
//directive loads SVG into DOM
angular.module('FFPA').directive('svgFloorplan', ['$compile', function ($compile) {
return {
restrict: 'A',
templateUrl: 'test.svg',
link: function (scope, element, attrs) {
var groups = element[0].querySelectorAll("g[id^='f3']")
angular.forEach(groups, function (g,key) {
var cubeElement = angular.element(g);
//Wrap the cube DOM element as an Angular jqLite element.
cubeElement.attr("cubehvr", "");
$compile(cubeElement)(scope);
})
}
}
}]);
The SVG diagram contains tags with unique identifiers, ie:
<g id="f3s362c12"></g>
Directive Two loads JSON data from an injected service that corresponds to each of the SVG tag id's.
//filters json based on hover item
dataService.getData().then(function(data) {
thisData = data.filter(function (d) {
return d.seatId.trim() === groupId
});
As shown above, Directive Two also adds a hover event function that filters the JSON data based on the tag that was hovered over.
IE: If a user hovers over , a filter in the directive would return this JSON record:
{"Id":1,
"empNum":null,
"fName":" Bun E.",
"lName":"Carlos",
...
"seatId":"f3s362c12 ",
"floor":3,
"section":"313 ",
"seat":"12 "}
DIRECTIVE TWO:
//SVG hover directive/filter match json to svg
angular.module("FFPA").directive('cubehvr', ['$compile', 'dataService', function ($compile, dataService) {
return {
restrict: 'A',
scope: true,
link: function (scope, element, attrs) {
//id of group
scope.elementId = element.attr("id");
//alert(scope.elementId);
var thisData;
//function call
scope.cubeHover = function () {
//groupId is the id of the element hovered over.
var groupId = scope.elementId;
//filters json based on hover item
dataService.getData().then(function(data) {
thisData = data.filter(function (d) {
return d.seatId.trim() === groupId
});
//return data.seatId === groupId
scope.gData = thisData[0];
alert(thisData[0].fName + " " + thisData[0].lName + " " + thisData[0].deptId);
});
//after we get a match, we need to display a tooltip with save/cancel buttons.
$scope.empData = $scope.gData;
};
element.attr("ng-mouseover", "cubeHover()");
element.removeAttr("cubehvr");
$compile(element)(scope);
}
//,
//controller: function($scope, $element){
// $scope.empData = $scope.gData;
//}
}
}]);
The problem we now have is now (besides having minimal Angular experience and facing a unique and difficult implementation problem) is that we're trying to implement a way to create a tooltop using a div tag and an angular scope variable that we can display when a user hovers over the SVG tag element (instead of a Javascript alert which is demonstrated in the Plunker POC link below).
Since the data is being driven by the directive and the directive is already taking "cubehvr" as a parameter:
angular.module("FFPA").directive('*cubehvr*', ['$compile', 'dataService', function ($compile, dataService)
We're stuck since we don't know how to set an HTML page scope directive or variable, say like this from our second directive:
<div uib-popover="Last Name: {{empData.lName}}"
popover-trigger="'mouseenter'"
type="div"
class="btn btn-default">Tooltip
</div>
Or as simple as say, this:
<div emp-info></div>
The div tooltips will have html buttons that call Web API Update functionality.
We have a scaled down POC Plunk here:
Also were thinking about using the Angular Bootstrap UI for the toolips:
Hope that makes sense.
//Edit. I read your question once again and go thru my answer. I didn't fully answer your question as it's multi-layered. Now I'll go thru all your concerns and try to answer them:
$scope is Model-View in MVVM design pattern, it glues your Template (View) and your Model together. In theory you could probably pass the $scope to the other directive, but I think it's an anti-pattern.
Communication between directives.There are at least 4 methods which you can use to communicate your directives:
Add popover to a SVG's child. Bootstrap has an ability to add popover to body instead of to parent element. It's useful for SVGs: https://angular-ui.github.io/bootstrap/#!#popover
I refactored your code to use two directives, and the data is loaded in controller. One directive wraps the popover and the second one passes the data, also the popover uses template now, so it's being compiled with angular:
var app = angular.module('FFPA', ['ngAnimate', 'ngSanitize', 'ui.bootstrap']);
//controller
app.controller('myCtrl', function ($scope, dataService) {
$scope.test = 'test';
dataService.getData().then(function(data) {
$scope.dataset = data.reduce(function (obj, item) {
obj[item.seatId.trim()] = item;
item.fullName = item.fName + ' ' + item.lName;
return obj;
}, {});
});
});
angular.module('FFPA').service('dataService', function($http){
this.getData = function(){
return $http.get("data.json").then(
function(response){
return response.data;
}, function() {
return {err:"could not get data"};
}
);
}
});
//directive loads SVG into DOM
angular.module('FFPA').directive('svgFloorplan', ['$compile', function ($compile) {
return {
restrict: 'A',
templateUrl: 'test.svg',
scope: {
'dataset': '=svgFloorplan'
},
link: {
pre: function (scope, element, attrs) {
var groups = element[0].querySelectorAll("g[id^='f3']");
scope.changeName = function (groupId) {
if (scope.dataset[groupId] && scope.dataset[groupId].lastName.indexOf('changed') === -1) {
scope.dataset[groupId].lastName += ' changed';
}
}
groups.forEach(function(group) {
var groupId = group.getAttribute('id');
if (groupId) {
var datasetBinding = "dataset['" + groupId + "']";
group.setAttribute('svg-floorplan-popover', datasetBinding);
$compile(group)(scope);
}
});
}
}
}
}]);
angular.module('FFPA').directive('svgFloorplanPopover', ['$compile', function ($compile) {
return {
restrict: 'A',
scope: {
'person': '=svgFloorplanPopover'
},
link: function (scope, element, attrs) {
scope.changeName = function () {
if (scope.person && scope.person.fullName.indexOf('changed') === -1) {
scope.person.fullName += ' changed';
}
}
scope.htmlPopover = 'popoverTemplate.html';
element[0].setAttribute('uib-popover-template', "htmlPopover");
element[0].setAttribute('popover-append-to-body', 'true');
element[0].setAttribute('popover-trigger', "'outsideClick'");
element[0].querySelector('text').textContent += '{{ person.fullName }}';
element[0].removeAttribute('svg-floorplan-popover');
$compile(element)(scope);
}
}
}]);
And your HTML body now looks like:
<body style="background-color:#5A8BC8;">
<div ng-app="FFPA" ng-controller="myCtrl">
<div svg-floorplan="dataset"></div>
</div>
</body>
HTML for popover:
<div><button type="button" class="btn btn-default" ng-click="changeName()">{{ person.fullName }}</button></div>
Here is working plunker: http://plnkr.co/edit/uHgnZ1ZprZRDvL0uIkcH?p=preview