Implement the general update pattern for an svg path - angularjs

I have a custom chart that is loaded with some initial values on page load. When an event happens, I update the chart with some indicative arrows that show some trend. The d3 part resides inside an angular directive that gets its updated data when there is a change to the dataset from various parts of the page. What I have currently seems to work, but few path elements are not showing up. I wonder if I messed up something in the general update pattern
A snippet of the part where I have the general update pattern
ngApp.directive("customChart", function ($window) {
return {
restrict : "A",
template : "<svg width='320' height='200'></svg>",
scope : {
data : "=chartData"
},
link : function (scope, elem, attrs) {
scope.line = {
/*line function*/
};
scope.setChartDimensions = function () {
// . . .
}
scope.render = function (dataset) {
aData = dataset.arrows ? dataset.arrows : undefined;
// . . .
if (arrowData) {
// . . .
var path = svg
.selectAll("path")
.data(pathCoordinates, function (d) {
return d;
});
path.enter()
.append("path")
.attr("d", function (d) {
return scope.line(d) + "Z";
})
.attr("class", "enter");
path.exit().remove();
}
}
scope.$watchCollection('data', function (newValue, oldValue) {
scope.render(newValue);
});
scope.setChartDimensions();
}
}
}
When an event happens some of the arrow data is not being recognized as new or something and those arrows are not being rendered despite that data being available at the time.
Fiddle for the entire code: https://jsfiddle.net/animeshb/tbpnx0xd

Related

Angular eventListener from Directive

I have directive, essentially a button, that should only show on certain conditions. It uses addEventListener('scroll') to capture the scroll of a <ion-content> element in the containing page. This works fine for one page/view but when navigating to another page/view the scroll event is not fired?
'use strict';
angular.module('Fmpclient')
.directive('fmpRefreshButton', ['$window', '$ionicScrollDelegate', 'RefreshButtonService', function ($window, $ionicScrollDelegate, RefreshButtonService) {
var hideDelayTime = 3500;
var isButtonVisible = false;
var lastYPos = 0; // store the last Y Position
var timerId = null; // store a setTimeout id for later use
var $scrollEl = null;
var refreshButton = null;
/* __ code snipped for brevity __ */
/**
Work out the direction of scroll so we can
either hide or show the refresh button according to AC
#method onScroll
#public
*/
function onScroll () {
var scrollTop = $ionicScrollDelegate.getScrollPosition().top;
if(scrollTop > lastYPos) {
// Scrolling DOWN - hide refresh button
hideRefreshButton();
} else {
// Scrolling UP - show refresh button
shouldRefreshFeed();
}
// Store the last Y position to determine
// direction of scroll on next iteration
lastYPos = scrollTop;
}
/**
Setup directive elements on init
#method init
#public
*/
function init (){
console.log('directive::init()');
RefreshButtonService.setRefreshFeed(false);
$scrollEl = document.getElementById('ionContentScroll');
if($scrollEl){
$scrollEl.addEventListener('scroll', onScroll);
}
}
function _link(scope, element) {
refreshButton = element[0];
init();
}
return {
templateUrl: 'app/app/refresh-button/refresh-button.directive.html',
restrict: 'AE',
scope: true,
link: _link,
};
}
]);
As suggested by Scott, in the comment above, the solution to my rather trivial problem was to pass in the id as a parameter to the directive to ensure the id can be unique for each usage:
In the Directive scope:
scope: {
scrollElement: '='
},
and passed in on the Directive template:
<my-directive scroll-element="ionFeedScroll">

AngularJS - huge directive

I have a website that has to display different set of data on a map. The map will always be the same - Some areas with 'onhover' effect and tooltip.
There is about 10 different sets of data.
I created a directive to display the map
Directive (only draw the map)
angular.module('PluvioradApp.controllers')
.directive('map', function() {
return {
restrict: 'E',
link: function(scope, element, attrs) {
var svg = d3.select(element[0])
.append("div")
.classed("svg-container", true) //container class to make it responsive
.append("svg")
//responsive SVG needs these 2 attributes and no width and height attr
.attr("preserveAspectRatio", "xMinYMin meet")
.attr("viewBox", "0 0 1000 500")
//class to make it responsive
.classed("svg-content-responsive", true);
var g = svg.append("g");
//Scale / translate / ...
var lineFunction = d3.svg.line()
.x(function(d) { return (d[0]+50000)/500; })
.y(function(d) { return (-d[1]+170000)/500; })
.interpolate("linear");
//Draw map
var path = g.selectAll('path')
.data(data.areas)
.enter().append("path")
.attr("d", function(d){ return lineFunction(d.borders)})
.attr("fill", "#D5708B")
.on('mouseover', function(d) {
d3.select(this).style('fill', 'orange');
d3.select(this).text(function(d){return "yeah";})
})
.on('mouseout', function(d) {
g.selectAll('path').style('fill', '#D5708B');
});
// zoom and pan
var zoom = d3.behavior.zoom()
.on("zoom", function() {
g.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
g.selectAll("path")
.attr("d", function(d){ return lineFunction(d.borders)});
});
svg.call(zoom);
}
}
});
My idea was to get the data to display from the controller (it comes from an API) and send it to the directive. Inside the above directive I will add a big switch or multiple if to display the correct data with the correct colors, size,...
I am sure that there is another way to split the work.
For example :
1st directive to display the map. It can be reuse multiple time
2nd directive to display set 1
3rd directive to display set 2
If this is the correct way, how can I achieve this ?
Additional information
I have a menu with a dropdown to select which data will be displayed. For the moment, the items redirect to a page containing the map directive above
Have a folder where you will have a bunch of service, where each service will be one of your data set.
Set1Service, Set2Service. etc.
Each of this will have own logic.
Have a factory service which will return one of your service.
for example:
(new FactoryService())->get('dataSetItem'); //this will return one of services from point 1.
Inject FactoryService in you directive use it.
In factory you will have the logic how to parse your data set, to determine what DataSetService you will have to return
This is extensible an easy to use.
All that I described are more related to Strategy and Factory pattern, you can read more about those and this will help you to have more abstract implementation.
angular.module('PluvioradApp.controllers')
.directive('map', function(factoryService) {
return {
restrict: 'E',
scope: {
dataSet: '='
}
link: function(scope, element, attrs) {
//all your code
var dataSetService = factoryService.get(scope.dataset);
var result = dataSetService.seed(d3);
}
}).service('factoryService', function() {
this.get = function(name) {
var service = null;
switch (name) {
case 'set1'
service = new DataSet1();
break;
case 'set2'
service = new DataSet2();
break;
}
return service;
}
});
function DataSet1() {
}
DataSet1.prototype.seed = function(d3) {
//d3 logic you have access here;
}
function DataSet2() {
}
DataSet2.prototype.seed = function(d3) {
//d3 logic you have access here;
}

Adding a UI-Bootstrap Element to DOM inside Directive

I am new to angular and I am trying to figure out the following problem. When the user highlights some text, I would like a ui-bootstrap popover to surround the highlighted text. Since this would manipulate the DOM I think I need to use a directive for this. I was able to successfully implement a simpler version of this problem here
app.directive('selectOnClick', function ($window) {
return {
link: function (scope, element) {
element.on('click', function () {
var span = document.createElement("span");
span.style.fontWeight = "bold";
span.style.color = "green";
if (window.getSelection) {
var sel = window.getSelection();
if (sel.rangeCount) {
var range = sel.getRangeAt(0).cloneRange();
range.surroundContents(span);
sel.removeAllRanges();
sel.addRange(range);
}
}
});
}
}
});
In the above code I am able to surround the highlighted text with a span tag. However I would like to instead use a ui-bootstrap popover. I tried replacing the span part with var popover=angular.element("<a href=#' uib-popover='hello' popover-title='hello'></a>"); but this did not work. Am I on the right track or would this approach not work with a ui-bootstrap element?
UPDATED
Here is my attempt at adding the the popover element
app.directive('selectOnClick', function ($window, $compile) {
return {
link: function (scope, element) {
element.on('click', function () {
var popover=angular.element("<a href=#' uib-popover='hello' popover-title='hello'></a>");
if (window.getSelection) {
var sel = window.getSelection();
if (sel.rangeCount) {
var range = sel.getRangeAt(0).cloneRange();
range.surroundContents($compile(popover));
sel.removeAllRanges();
sel.addRange(range);
}
}
});
}
}
});
Unfortunately I am getting the error TypeError: Failed to execute 'surroundContents' on 'Range': parameter 1 is not of type 'Node'. on the line range.surroundContents($compile(popover)); So I suppose $compile(popover) is not the correct type. Can it be converted to Node type somehow?

ngMessages with custom validator in parent controller

I'm trying to figure out how to wire up a custom validator with ngMessages that has access to the parent scope. I have an input field for an address and onBlur I want to do a geolocate of the address and update the position of a marker on a map (by setting two variables on the controllers this).
Custom validator examples use directives (and I have followed those for a basic example) but can't move from their to geolocation, as I'm struggling with Directive to parent Controller communication in the context of ES6, ControllerAs,...:
I've started off trying to access the parent controller from the link function with scope.$parent (How to access parent scope from within a custom directive *with own scope* in AngularJS?) but I'm using ES6 classes and it just did not seem to work.
now I'm thinking about passing in the parent function to the Directive, but the function is complaining that it cannot find elements of the controller's normal scope, so that means it cannot update the marker's position.
Grateful for advice on wiring this up.
Here's where I got to in the second instance
var checkAddressDir = function($timeout) {
return {
require : 'ngModel',
scope: {
geolookup: '&'
},
link : function(scope, element, attrs, ngModel) {
function setAsLoading(bool) {
ngModel.$setValidity('recordLoading', !bool);
}
ngModel.$parsers.push(function(value) {
if(!value || value.length == 0) return;
setAsLoading(true);
scope.geolookup()(value)
// scope.$parent.findAddress()
.then( res => {
// I'll use res to update ngModel.$setValidity
console.log(res);
setAsLoading(false);
});
// THE FOLLOWING SERVED TO GET ME STARTED
// $timeout(function() {
// console.log("timeout");
// setAsLoading(false);
// }, 1000);
return value;
})
}
}
}
And this is the controller function I need to be able to use with the controller's scope
findAddress(address) {
return this.$q( (resolve, reject) => {
mygeocoder.geocode(myOptions, (res, stat) => {
if (stat === google.maps.GeocoderStatus.OK) {
if (res.length > 1) {
console.log(`Need unique result, but got ${res.length}`);
return "notUnique";
}
var loc = res[0].geometry.location;
this.resto.lat = loc.lat();
this.resto.lng = loc.lng();
this.zoom = 16;
this.$scope.$apply();
return "good";
} else {
console.log(" not found - try again?");
return "notFound";
}
});
});

Text Placeholders in CKEDITOR (angular context)

I am not very familiar with the CKEDITOR API yet and now I got stuck trying to find the way to create placeholders inside of the CKEDITOR editable area.The expected behaviour for the placeholder - to dissappear on user interaction with it, allowing to edit the content instead.
I know that there is already a placeholder plugin (http://ckeditor.com/addon/placeholder) but its behaviour is not what I am looking for.
To be more specific, the question is: is it possible to subscribe for some events on the particular element inside of the CKEDITOR?
Working in the angular context I am able to compile my html before it is passed to the CKEDITOR ng-model
$scope.html = $compile('<div><span text-placeholder >Placeholder</span></div>')($scope).html();
But then I fail trying to set click events inside of the directive:
.directive('textPlaceholder', [ function () {
return {
restrict: 'A',
link: function ($scope, $element) {
//THIS DOES NOT WORK UNFORTUNATELY
$element.on('click', function () {
console.log('clicked');
})
}
}
}])
Any thoughts?
UPDATE: For now I came up with the solution to implement simple plugin and then reference it in the CKEDITOR config:
(function () {
CKEDITOR.plugins.add('text-placeholder', {
init: function (editor) {
editor.on('key', function (evt) {
var el = $(CKEDITOR.instances.editor1.getSelection().getNative().baseNode.parentElement);
if (el.hasClass('text-placeholder')) {
el.remove();
}
});
}
});
})();
Looks ugly for me. Any feedback is appreciated.
This seems to be a final Solution:
CKEDITOR.plugins.add('text-placeholder', {
init: function (editor) {
editor.on('contentDom', function () {
var editable = editor.editable();
editable.attachListener(editable, 'click', function (event) {
var $placeholder = $(event.data.$.target).closest('.text-placeholder');
if ($placeholder.length > 0) {
var selection = editor.getSelection();
selection.selectElement(selection.getStartElement());
}
});
});
}
});
This applies the selection on the element with "text-placeholder" class when user focuses it inside of the editable area
Update:
See example
You inspired me to write one myself, using the above example as a starting point. In my use case I wanted to take placeholder text from an attribute on the editor -- data-placeholder -- and display it in the editor. When the editor gets focus, the placeholder text disappears. When the editor blurs -- if no user content has been entered -- the placeholder text is displayed again. Additionally, I set a data-placeholder-showing attribute so that I can, for example, use CSS to make the placeholder text gray. Here's my code:
CKEDITOR.plugins.add('text-placeholder', {
init: function (editor) {
var placeholder = editor.element.getAttribute('data-placeholder');
editor.on('contentDom', function () {
if (placeholder) {
editor.setData(placeholder);
editor.element.setAttribute('data-placeholder-showing', true);
}
});
editor.on('focus', function() {
if (editor.getData() === placeholder) {
editor.element.setAttribute('data-placeholder-showing', false);
editor.setData('');
}
});
editor.on('blur', function() {
if (placeholder && editor.getData().length === 0) {
editor.element.setAttribute('data-placeholder-showing', true);
editor.setData(placeholder);
}
});
}
});

Resources