Подтвердить что ты не робот

Включение шаблона SVG в директиву Angularjs

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

И это моя директива

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

Но это то, что элемент в конечном итоге выглядит как в браузере. Заполнение дважды не является опечаткой.

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

Здесь jsfiddle http://jsfiddle.net/RG2CF/

Я пытаюсь сделать то, что не могу, или я делаю что-то неправильно?

Спасибо

EDIT: я должен добавить, что проблема может быть связана с тем фактом, что шаблон svg rect помещается в xhtml вместо svg, но я не уверен, как я могу заставить это или использовать пространство имён в svg, если это на самом деле решение.

4b9b3361

Ответ 1

Существует свойство templateNamespace, которое вы можете установить в svg:

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

Вот ссылка в документации.

Ответ 2

Angular 1.3 Обновление!

Мой оригинальный ответ ниже был гигантским взломом в лучшем случае. Вам действительно нужно обновить до Angular 1.3 и установить свойство templateNamespace:'svg' в вашей директиве. Посмотрите ниже на @Josef Pfleger ответ на пример: Включая шаблон SVG в директиве Angularjs


Старый ответ

В случае, если кто-то столкнется с этим в будущем, мне удалось создать функцию компиляции, которая работает с шаблонами svg. Его немного уродливое прямо сейчас, поэтому помощь по рефакторингу очень ценится.

живой пример: 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');
      };
    }
  })

прецедент:

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
  };
});

Ответ 3

В настоящее время теги SVG не поддерживают динамическое добавление тегов, по крайней мере, в Chrome. Он даже не отображает новый прямоугольник, и даже если бы вы просто добавили его непосредственно с помощью DOM или JQuery.append(). Взгляните на просто попытку сделать это с помощью 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" />');
});

Я предполагаю, что вы увидите какое-то действительно сумасшедшее поведение, независимо от того, что вы делаете таким образом. Похоже, это проблема с браузером, и не так много Angular. Я бы рекомендовал сообщить об этом проекту Chromium.

EDIT:

Похоже, что это может сработать, если вы добавите его через программный код DOM на 100%: Как сделать Chrome перерисовывать SVG динамически добавленный контент?

Ответ 4

Использование прокладки innerHTML для SVG (он же "innerSVG" ) предоставляет еще одну часть этой головоломки, как альтернативу Джейсону Более проницательному ответу. Я чувствую, что этот подход innerHTML/innerSVG более изящный, потому что для него не требуется специальная функция компиляции, такая как ответ Джейсона, но у него есть хотя бы один недостаток: он не работает, если в директиве задано значение "replace: true". (Подход innerSVG обсуждается здесь: Есть ли замена innerHTML в SVG/XML?)

// 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
});

Здесь Plunker, чтобы попробовать: http://plnkr.co/edit/nEqfbbvpUCOVC1XbTKdQ?p=preview

Причина этого заключается в следующем: элементы SVG живут в пространстве имен XML, http://www.w3.org/2000/svg, и поэтому они должны либо быть обрабатывается с помощью DOMParser() или создается с помощью createElementNS() (как в ответе Джейсона), оба из которых могут правильно задать пространство имен. Было бы неплохо, если бы этот процесс использования SVG был так же прост, как HTML, но, к сожалению, их структуры DOM тонко отличаются друг от друга, что приводит к необходимости поставлять собственный innerHTML shim. Теперь, если бы мы могли бы получить "replace: true", чтобы работать с этим, это будет что-то хорошее!

EDIT: обновлен цикл appendChild() для изменения содержимого обходного пути во время цикла.

Ответ 5

angular не обращает внимания на пространства имен при создании элементов (хотя элементы, похоже, клонированы с именами имен родителя), поэтому новые директивы внутри <svg> не будут работать без пользовательской функции компиляции.

Что-то вроде следующего может обойти проблему, создав сами элементы dom. Для этого вам необходимо иметь 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);