简体   繁体   English

在Angularjs指令中包含SVG模板

[英]Including SVG template in Angularjs directive

<div ng-app="testrectApp">
<svg>
    <rect height="10" width="10" style="fill: #00ff00" />
    <testrect />
</svg>
</div>

And this is my directive 这是我的指示

module.directive('testrect', function() {
    return {
        restrict: 'E',
        template: '<rect top="20" left="20" height="10" width="10" style="fill: #ff00ff" />',
        replace: true
    };
});

But this is what the element ends up looking like in the browser. 但这就是元素最终看起来像在浏览器中。 The fill twice is not a typo. 填充两次不是拼写错误。

<rect top="20" left="20" height="10" width="10" style="fill: #ff00ff;fill: #ff00ff"></rect>

Here's a jsfiddle http://jsfiddle.net/RG2CF/ 这是一个jsfiddle http://jsfiddle.net/RG2CF/

Is what I'm trying to do not possible or am I doing something wrong? 是我想做的不可能还是我做错了什么?

Thanks 谢谢

EDIT: I should add that the issue may have to do with the fact that the template svg rect is namespaced to xhtml instead of svg, but I'm unsure of how I can force this or namespace it back to svg, if that is actually the solution. 编辑:我应该补充一点,这个问题可能与模板svg rect被命名为xhtml而不是svg的事实有关,但我不确定如何强制这个或命名空间它回到svg,如果实际上是解决方案。

There is a templateNamespace property you can set to svg : 有一个templateNamespace属性,你可以设置为svg

module.directive('testrect', function() {
    return {
        restrict: 'E',
        templateNamespace: 'svg',
        template: '<rect .../>',
        replace: true
    };
});

Here is a link to the documentation. 这是文档的链接

Angular 1.3 Update! Angular 1.3更新!

My original answer below was a giant hack at best. 我在下面的原始答案充其量只是一个巨大的黑客。 What you should really do is update to Angular 1.3 and set the templateNamespace:'svg' property on your directive. 你应该做的是更新Angular 1.3并在你的指令上设置templateNamespace:'svg'属性。 Look below at @Josef Pfleger answer for an example: Including SVG template in Angularjs directive 下面以@Josef Pfleger的答案为例: 在Angularjs指令中包含SVG模板


Old Answer 老答案

In case anyone comes across this in the future, I was able to create a compile function that works with svg templates. 如果将来遇到这种情况,我可以创建一个与svg模板一起使用的编译功能。 Its a bit ugly right now, so refactoring help is much appreciated. 它现在有点难看,所以非常感谢重构帮助。

live example: http://plnkr.co/edit/DN2M3M?p=preview 实例: http//plnkr.co/edit/DN2M3M?p = preview

app.service('svgService', function(){

    var xmlns = "http://www.w3.org/2000/svg";
    var compileNode = function(angularElement){
      var rawElement = angularElement[0];

      //new lines have no localName
      if(!rawElement.localName){
        var text = document.createTextNode(rawElement.wholeText);
        return angular.element(text);
      }
      var replacement = document.createElementNS(xmlns,rawElement.localName);
      var children = angularElement.children();

      angular.forEach(children, function (value) {
        var newChildNode = compileNode(angular.element(value));
        replacement.appendChild(newChildNode[0]);
      });

      if(rawElement.localName === 'text'){
        replacement.textContent = rawElement.innerText;
      }

      var attributes = rawElement.attributes;
      for (var i = 0; i < attributes.length ; i++) {
        replacement.setAttribute(attributes[i].name, attributes[i].value);
      }

      angularElement.replaceWith(replacement);

      return angular.element(replacement);
    };

    this.compile = function(elem, attrs, transclude) {
      compileNode(elem);

      return function postLink(scope, elem,attrs,controller){
//        console.log('link called');
      };
    }
  })

use case: 用例:

app.directive('ngRectAndText', function(svgService) {
  return {
    restrict: 'E',
    template: 
    '<g>'
    + '<rect x="140" y="150" width="25" height="25" fill="red"></rect>'
    + '<text x="140" y="150">Hi There</text>'
    + '</g>'
    ,
    replace: true,
    compile: svgService.compile
  };
});

Currently SVG tags don't support dynamic addition of tags, at least in Chrome. 目前,SVG标记不支持动态添加标记,至少在Chrome中是这样。 It doesn't even display the new rect, nor would it even if you just added it directly with the DOM or JQuery.append(). 它甚至不显示新的rect,即使你直接用DOM或JQuery.append()添加它也不会。 Have a look at just trying to do it with JQuery 看看只是尝试使用JQuery

// this doesn't even work right. Slightly different error than yours.
$(function() {
    $('svg').append('<rect top="20" left="20" height="10" width="10" style="fill: #ff00ff" />');
});

My guess is you're going to see some really crazy behavior no matter what you do in this manner. 我的猜测是,无论你以这种方式做什么,你都会看到一些非常疯狂的行为。 It seems like it's an issue with the browser, and not so much Angular. 这似乎是浏览器的一个问题,而不是Angular。 I'd recommend reporting it to the Chromium project . 我建议将其报告给Chromium项目

EDIT: 编辑:

It looks like it might work if you add it via the DOM 100% programmatically: How to make Chrome redraw SVG dynamically added content? 看起来如果你通过DOM 100%以编程方式添加它可能会有效: 如何让Chrome重绘SVG动态添加内容?

Using an innerHTML shim for SVG (aka "innerSVG") provides another piece of this puzzle, as an alternative to Jason More's insightful answer. 使用innerHTML shim for SVG(又名“innerSVG”)提供了另一个这个难题,作为Jason More的深刻回答的替代品。 I feel this innerHTML/innerSVG approach is more elegant because it does not require a special compile function like Jason's answer, but it has at least one drawback: it does not work if "replace: true" is set on the directive. 我觉得这个innerHTML / innerSVG方法更优雅,因为它不需要像Jason的答案那样的特殊编译函数,但它至少有一个缺点:如果在指令上设置了“replace:true”,它就不起作用。 (The innerSVG approach is discussed here: Is there some innerHTML replacement in SVG/XML? ) (这里讨论了innerSVG方法: 在SVG / XML中是否有一些innerHTML替换?

// Inspiration for this derived from a shim known as "innerSVG" which adds
// an "innerHTML" attribute to SVGElement:
Object.defineProperty(SVGElement.prototype, 'innerHTML', {
  get: function() {
    // TBD!
  },
  set: function(markup) {
    // 1. Remove all children
    while (this.firstChild) {
      this.removeChild(this.firstChild);
    }

    // 2. Parse the SVG
    var doc = new DOMParser().parseFromString(
        '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">'
        + markup
        + '</svg>',
        'application/xml'
    );

    // 3. Import the parsed SVG and grab the childNodes
    var contents = this.ownerDocument.importNode(doc.documentElement, true).childNodes;

    // 4. Append the childNodes to this node
    // Note: the following for loop won't work because as items are appended,
    // they are removed from contents (you'd end up with only every other
    // element getting appended and the other half remaining still in the
    // original document tree):
    //for (var i = 0; i < contents.length; i++) {
    //  this.appendChild(contents[i]);
    //}
    while (contents.length) {
      this.appendChild(contents[0]);
    }
  },
  enumerable: false,
  configurable: true
});

Here's a Plunker to try: http://plnkr.co/edit/nEqfbbvpUCOVC1XbTKdQ?p=preview 这是一个尝试的Plunker: http ://plnkr.co/edit/nEqfbbvpUCOVC1XbTKdQ?p = preview

The reason why this works is: SVG elements live within the XML namespace, http://www.w3.org/2000/svg , and as such, they must either be parsed with DOMParser() or instantiated with createElementNS() (as in Jason's answer), both of which can set the namespace appropriately. 这样做的原因是:SVG元素存在于XML命名空间中, http://www.w3.org/2000/svg ,因此,它们必须使用DOMParser()进行解析或使用createElementNS()实例化(如在Jason的回答中),两者都可以适当地设置名称空间。 It would be nice if this process of using SVG was as easy as HTML, but unfortunately their DOM structures are subtly different which results in the need to supply our own innerHTML shim. 如果这个使用SVG的过程像HTML一样容易,那就太好了,但不幸的是它们的DOM结构略有不同,导致需要提供我们自己的innerHTML垫片。 Now, if we could also get "replace: true" to work with this, that will be something good! 现在,如果我们也可以使用“replace:true”来处理这个问题,那将是件好事!

EDIT: Updated the appendChild() loop to workaround contents changing while loop runs. 编辑:更新了appendChild()循环以解决在循环运行时更改内容的问题。

angular doesn't pay attentention to namespaces when creating elements (though elements seem to be cloned with namepsace of a parent), so new directives inside <svg> won't work without a custom compile function. angular在创建元素时不会注意名称空间(尽管元素似乎是用父项的namepsace克隆的),因此如果没有自定义编译函数, <svg>新指令将无法工作。

Something like the following can get around the issue by doing the creation of the dom elements itself. 像下面这样的东西可以通过创建dom元素本身来解决这个问题。 For this to work, you need to have xmlns="http://www.w3.org/2000/svg" as an attribute on the root element of template and your template must be valid XML (having only one root element). 为此,您需要将xmlns="http://www.w3.org/2000/svg"作为template根元素的属性,并且您的模板必须是有效的XML(只有一个根元素)。

directiveGenerator = function($compile) {
  var ret, template;
  template = "<g xmlns=\"http://www.w3.org/2000/svg\"> +
             "<rect top=\"20\" left=\"20\" height=\"10\" width=\"10\" style=\"fill: #ff00ff\" />" +
             "</g>";
  ret = {
    restrict: 'E',
    compile: function(elm, attrs, transclude) {
      var templateElms;
      templateElms = (new DOMParser).parseFromString(template, 'application/xml');
      elm.replaceWith(templateElms.documentElement);
      return function(scope, elm, attrs) {
        // Your link function
      };
    }
  };
  return ret;
};

angular.module('svg.testrect', []).directive('testrec', directiveGenerator);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM