AngularJS SVG Path directive - javascript

Bit of a short question, but wondering why this is happening:
In the example posted below, there are two SVG elements, each containing a directive assigned to a element. The first directive doesn't use a template, and is the setup I currently use to assign actions to paths.
The second directive is what I was trying to do in order to clean up my code a bit, but it won't be drawn.
I googled a bit around, and read there is a difference between SVG & DOM nodes? But since both ng-repeat's are working, I'm a bit puzzled on what is going on.
Link to plunkr: http://plnkr.co/edit/cok6O58SOUyaGHG5Jtu5?p=preview
angular.module('app', []).controller("AppCtrl", function($scope) {
$scope.items = [1,2,3,4];
}).directive('svgPathNoReplace', function() {
return {
restrict: 'A',
scope: true,
link: function(scope, element, attrs) {
}
}
}).directive('svgPathReplace', function() {
return {
restrict: 'A',
replace: true,
template: '<path id="1" d="M 85.750001,0 C 38.393651,0 0,39.02444 0,87.15625 c 0,48.13181 38.393651,87.1875 85.750001,87.1875 C 133.10635,174.34375 171.5,135.28806 171.5,87.15625 171.5,39.02444 133.10635,0 85.750001,0 z m 0,1.5 C 132.28206,1.5 170,39.8217 170,87.15625 c 0,47.33455 -37.71794,85.6875 -84.249999,85.6875 C 39.217941,172.84375 1.5,134.4908 1.5,87.15625 1.5,39.8217 39.217941,1.5 85.750001,1.5 z" '+
'style="stroke:#000000;stroke-width:1px;"></path>'
}
});

The most current beta (1.3) allows another property on the directive called type where you can specify svg content now.
ie.
return {
restrict : 'AE',
type : 'svg',
template : '<path></path>'
link : function (..)
}

Your initial research was correct -- in AngularJS, templates are rendered as DOM nodes, not SVG nodes. This means the directive's template is rendering your <path> tag as a "dummy" non-standard HTML tag that does nothing.
In other words, although HTML normally recognizes path nodes as non-standard nodes, it's preconfigured to know how to handle them using SVG. But when HTML sees a non-standard DOM node rendered by the template (which in this example just happens to be named path), it doesn't recognize it and therefore does nothing. Think of it as trying to use <foo></foo>.
However, this doesn't mean the ng-repeat directive won't work on the custom DOM node, since it doesn't care what type of node (i.e., standard or custom) it is repeating.
Updated Plunker
The above example solves your problem by manually creating the HTML node using the directive's link function instead of its template key. The helper function creates the path node in a way that can be recognized by SVG, which eliminates the need to use a template.
My answer is inspired by this solution.

Related

Dynamic directive controller using name property

I'm trying to implement directive with dynamic controller so that I can bind controller based on some condition just like Todd Motto has shown it here
Everything works fine expect that I can't send object property as a name to the directive like,
<directive-with-dynamic-controller ctrl="someObj.prop"></directive-with-dynamic-controller>
I've even tried this, but to no avail:
<directive-with-dynamic-controller ctrl="{{someObj.prop}}"></directive-with-dynamic-controller>
It gives error like this:
Argument is not a function, got undefined
Any ideas how I can solve this? Or any other way?
Thanks!
Any ideas how I can solve this? Or any other way? Thanks!
The problem is in the order of execution. Something that was left out in the mentioned article is the fact that you cannot pass an expression in the case of setting a "dynamic" controller (so much for dynamic).
If we look at the notation of a directives compile step, you would notice that there is no access to the current $scope.
This is because DOM compilation and controller initialisation happens before the angular parser kicks in and evaluates your expression(s).
As such, you cannot pass a $scope expression into the ctrl attribute, as it is simply a regular DOM attribute at this point in time. In essence, you are passing the raw string into the ctrl attribute.
<my-custom-dir ctrl="foo.bar"></my-custom-dir>
// Error: "foo.bar" is not a controller // is not a function // $minErrObscureStuffThatDoesnHelpYou.
I've been trying to figure out a slick way to get deferred directive compilation running for some time now, but to no avail...
One possible way of getting around this problem (ymmv):
.directive('...', function ($controller) {
controller: function ($scope, $element, $attrs) {
$attrs.$observe('ctrl', function (n, o) {
return $controller(n, {
$scope: $scope,
$element: $element,
$attrs: $attrs
});
});
}
});
Effectively, you would replace a pre-initialised controller (that does nothing) with the controller matched by the name you passed through your attrs.ctrl attribute. However, this would execute post-compilation - so I wouldn't seriously recommend it.
jsfiddle showing the order of execution
tl;dr There is currently no slick way to define a controller for a directive, based on a $scope expression. It has to be a raw string because compilation isn't scoped on a per-component basis, but more so in a 'global' order of execution.
DOM Compilation > Controller initialisation > Scope linkage fiddle

Can I inject JointJS as an AngularJS module like any other library?

I have an app with angular and I need to use this library http://www.jointjs.com/, So I downloaded the joint.min.js and joint.min.css and placed their routes in the index.html but I don't know what to put in the app.js to inject it and I keep getting injection error from angular. Is it possible that this is not the way to do it? I googled a lot but didn't find any approach. I will appreciate any help, thanks in advance!
If you want to render a Jointjs diagram in your angular application, then that is pretty easy to do. In my case I encapsulated the Jointjs code inside an angular directive and passed in the Jointjs graph object. The (simplified) directive looks like this:
(function () {
'use strict';
var app = angular.module('app');
app.directive('jointDiagram', [function () {
var directive = {
link: link,
restrict: 'E',
scope: {
height: '=',
width: '=',
gridSize: '=',
graph: '=',
}
};
return directive;
function link(scope, element, attrs) {
var diagram = newDiagram(scope.height, scope.width, scope.gridSize, scope.graph, element[0];
//add event handlers to interact with the diagram
diagram.on('cell:pointerclick', function (cellView, evt, x, y) {
//your logic here e.g. select the element
});
diagram.on('blank:pointerclick', function (evt, x, y) {
// your logic here e.g. unselect the element by clicking on a blank part of the diagram
});
diagram.on('link:options', function (evt, cellView, x, y) {
// your logic here: e.g. select a link by its options tool
});
}
function newDiagram(height, width, gridSize, graph, targetElement) {
var paper = new joint.dia.Paper({
el: targetElement,
width: width,
height: height,
gridSize: gridSize,
model: graph,
});
return paper;
}
}]);
})();
If you need to interact with your model through the diagram, use the Jointjs event handlers and hook them up to functions on your scope in the directive (as shown in the code above).
To use this in your view:
<joint-diagram graph="vm.graph" width="800" height="600" grid-size="1" />
In my case I create the graph in the first case by using the Jointjs graph.fromJSON function from my controller (strictly speaking, this is in a data service component that is called from my controller) and then just add this to the scope.
function getDiagram() {
return datacontext.getDiagram($routeParams.diagramId).then(function (data) {
vm.graph.fromJSON(JSON.parse(diagramJson));
});
}
This approach works OK for adding and removing elements and links from the diagram and for dragging things around. Your controller code just works on the graph object and all the updates to the diagram rendering are handled by Jointjs.
function addCircle(x, y, label) {
var cell = new joint.shapes.basic.Circle({
position: { x: x, y: y },
size: { width: 100, height: 100 },
attrs: { text: { text: label } }
});
graph.addCell(cell);
return cell;
};
Jointjs is a great library, but it is based on Backbone.js for databinding. The only problem I have found is that it doesn't play particularly well with angular in cases where you want to edit diagram element properties (e.g. the contained text) using angular. For example, I have a properties pane (an angular view) that is used to edit the selected diagram element properties.
I made a hacky workaround for this that I am too ashamed to put on SO ;o) I'm still learning about angular/joint/backbone so hope to have a better approach by the time I finish my project. If I do, I'll post it here. Maybe someone more expert than me could already do better though - I'd be glad to see a better approach posted here.
Overall, this directive works as an approach, but it feels like a superficial integration between Angular and Jointjs. Essentially the directive creates an "island of jointjs" inside the angular application. I would like to find a more "angular native" way of doing this, but maybe that would require a re-write of Jointjs to use angular instead of backbone...
P.s. If you already have jquery in your application, you can get a version of joint that excludes jquery from the Jointjs download page:
http://www.jointjs.com/download
Found a great GitHub repo that tries to do just what your asking. If its not exactly what you wanted its still a great inspiration source!

Adding directives to an element using the compile function

I'm trying to create a simple directive to avoid having bulky elements for hovering. This is what I have:
app.directive('ngHover', function(){
return {
compile: function($element,attr) {
$element.attr('ng-mouseenter',attr.ngHover + ' = true')
.attr('ng-mouseleave',attr.ngHover + ' = false')
.removeAttr('ng-hover');
}
}
})
The resulting element is what I would have wrote (and would have worked) but it doesn't seem to be added before angular uses $compile. I could use $compile manually but I want to understand why this doesn't work.
DEMO
This is how the compiler of Angular works.
Compiler is an Angular service which traverses the DOM looking for
attributes. The compilation process happens in two phases.
Compile: traverse the DOM and collect all of the directives. The
result is a linking function.
Link: combine the directives with a scope and produce a live view...
Which means that by the time when you are inside of compile function where you add the attributes, it's over and compiler will never discover and recognise ng-mouseenter and ng-mouseleave as directives. In order to achieve this you will need to trigger another round of compilation with $compile, as you said.
Also see this message and the whole thread. There you can see that it would work if you were setting extra directives on children of the current element, but not on itself.

ng-bind-html not processing INPUT

I would like to bind html with the content of $scope.value = "<input type=text name=a>"
Nothing is inserted inf the DOM, but if $scope.value = "Hello <i>Guys</i>" everything is fine.
Is there a limitation/bug with ng-bind-html? Is there a workaround?
I am using 1.2.4 version of angularJS
Thanks for your help, this is a big issue for my development
Christophe
Can't you use a Directive instead of that? This way I think you get rid of your problem
http://docs.angularjs.org/guide/directive
https://egghead.io/search?q=directive
A small example:
angular.module('myApp').directive('myDirective', function(){
return {
restrict: 'E',
replace: true,
// you can set 'transclude: true' instead of the following line to create a new scope but inheriting from the parent
scope: false, // this will make the directive have the same scope as the parent
templateUrl: 'my-html-template.html'// you can load the template like this
// You can also use 'template' and include the html code here
}
});

In Angular: are the compile function's pre and post methods the same as link's pre and post

In an angular directive's compile function there is a pre and post. Is this pre and post really the same as the link function?
For example in the code below, is the link function the same (shortcut if you will) as the pre and post of the compile function below it?
Link
....
link: {
pre: function(scope, elem, attr) {
//stuff
},
post: function(scope, elem, attr) {
//stuff
}
}
....
Compile...
....
compile: function(tElem, tAttrs){
return {
pre: function(scope, iElem, iAttrs){
//stuff
},
post: function(scope, iElem, iAttrs){
//stuff
}
}
}
.....
Compile is run first (and is usually where you maipulate your "template" dom elements). Link is run second, and is usually where you attach your directive to $scope.
They also run in a specific order, so you can use that fact when you're designing directives that require some "parent" directive setup in order to operate properly (like a tr:td sorta thing).
There's a really great article on timing for compile vs link you can take a look at for more clarity.
Also - there's a very low level stack answer to a similar question you might like (note that its NOT the one listed first, it's the one most upvoted).
So, What's the Difference?
So are compile pre/post link "the same" as the link function? You decide.
If you define compile on a directive, the framework ignores your link function (because the compile function is supposed to return pre/post link functions).
It's a little bit like link overloads compile.postLink and link.pre overloads compile.preLink.
When this overloading happens, are you aware of anything different happening (i.e. any other functionality being added) as supposed to just returning pre and post from compile?
If you look at the source code,
when the $directiveProvider registers the directives, if the compile property is missing and the link property exists, it creates a compile property that is an empty function that returns the link property.
So the answer is that the link functions returned by the compile function are the same as the link functions provided by the link property of the DDO. No other functionality is added.

Categories

Resources