Skip to content
This repository has been archived by the owner on Apr 12, 2024. It is now read-only.

Commit

Permalink
feat($compile): allow SVG and MathML templates via special type pro…
Browse files Browse the repository at this point in the history
…perty

Previously, templates would always be assumed to be valid HTML nodes. In some cases, it is
desirable to use SVG or MathML or some other language.

For the time being, this change is only truly meaningful for SVG elements, as MathML has
very limited browser support. But in the future, who knows?

Closes #7265
  • Loading branch information
caitp committed Apr 29, 2014
1 parent 28ef263 commit f0e12ea
Show file tree
Hide file tree
Showing 2 changed files with 139 additions and 3 deletions.
34 changes: 31 additions & 3 deletions src/ng/compile.js
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,19 @@
* * `M` - Comment: `<!-- directive: my-directive exp -->`
*
*
* #### `type`
* String representing the document type used by the markup. This is useful for templates where the root
* node is non-HTML content (such as SVG or MathML). The default value is "html".
*
* * `html` - All root template nodes are HTML, and don't need to be wrapped. Root nodes may also be
* top-level elements such as `<svg>` or `<math>`.
* * `svg` - The template contains only SVG content, and must be wrapped in an `<svg>` node prior to
* processing.
* * `math` - The template contains only MathML content, and must be wrapped in an `<math>` node prior to
* processing.
*
* If no `type` is specified, then the type is considered to be html.
*
* #### `template`
* replace the current element with the contents of the HTML. The replacement process
* migrates all of the attributes / classes from the old element to the new one. See the
Expand Down Expand Up @@ -1261,7 +1274,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
if (jqLiteIsTextNode(directiveValue)) {
$template = [];
} else {
$template = jqLite(trim(directiveValue));
$template = jqLite(wrapTemplate(directive.type, trim(directiveValue)));
}
compileNode = $template[0];

Expand Down Expand Up @@ -1676,7 +1689,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
}),
templateUrl = (isFunction(origAsyncDirective.templateUrl))
? origAsyncDirective.templateUrl($compileNode, tAttrs)
: origAsyncDirective.templateUrl;
: origAsyncDirective.templateUrl,
type = origAsyncDirective.type;

$compileNode.empty();

Expand All @@ -1690,7 +1704,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
if (jqLiteIsTextNode(content)) {
$template = [];
} else {
$template = jqLite(trim(content));
$template = jqLite(wrapTemplate(type, trim(content)));
}
compileNode = $template[0];

Expand Down Expand Up @@ -1813,6 +1827,20 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
}


function wrapTemplate(type, template) {
type = lowercase(type || 'html');
switch(type) {
case 'svg':
case 'math':
var wrapper = document.createElement('div');
wrapper.innerHTML = '<'+type+'>'+template+'</'+type+'>';
return wrapper.childNodes[0].childNodes;
default:
return template;
}
}


function getTrustedContext(node, attrNormalizedName) {
if (attrNormalizedName == "srcdoc") {
return $sce.HTML;
Expand Down
108 changes: 108 additions & 0 deletions test/ng/compileSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -768,6 +768,59 @@ describe('$compile', function() {
}).not.toThrow();
expect(nodeName_(element)).toMatch(/optgroup/i);
}));

if (window.SVGAElement) {
it('should support SVG templates using directive.type=svg', function() {
module(function() {
directive('svgAnchor', valueFn({
replace: true,
template: '<a xlink:href="{{linkurl}}">{{text}}</a>',
type: 'SVG',
scope: {
linkurl: '@svgAnchor',
text: '@?'
}
}));
});
inject(function($compile, $rootScope) {
element = $compile('<svg><g svg-anchor="/foo/bar" text="foo/bar!"></g></svg>')($rootScope);
var child = element.children().eq(0);
$rootScope.$digest();
expect(nodeName_(child)).toMatch(/a/i);
expect(child[0].constructor).toBe(window.SVGAElement);
expect(child[0].href.baseVal).toBe("/foo/bar");
});
});
}

// MathML is only natively supported in Firefox at the time of this test's writing,
// and even there, the browser does not export MathML element constructors globally.
// So the test is slightly limited in what it does. But as browsers begin to
// implement MathML natively, this can be tightened up to be more meaningful.
it('should support MathML templates using directive.type=math', function() {
module(function() {
directive('pow', valueFn({
replace: true,
transclude: true,
template: '<msup><mn>{{pow}}</mn></msup>',
type: 'MATH',
scope: {
pow: '@pow',
},
link: function(scope, elm, attr, ctrl, transclude) {
transclude(function(node) {
elm.prepend(node[0]);
});
}
}));
});
inject(function($compile, $rootScope) {
element = $compile('<math><mn pow="2"><mn>8</mn></mn></math>')($rootScope);
$rootScope.$digest();
var child = element.children().eq(0);
expect(nodeName_(child)).toMatch(/msup/i);
});
});
});


Expand Down Expand Up @@ -1612,6 +1665,61 @@ describe('$compile', function() {
$rootScope.$digest();
expect(nodeName_(element)).toMatch(/optgroup/i);
}));

if (window.SVGAElement) {
it('should support SVG templates using directive.type=svg', function() {
module(function() {
directive('svgAnchor', valueFn({
replace: true,
templateUrl: 'template.html',
type: 'SVG',
scope: {
linkurl: '@svgAnchor',
text: '@?'
}
}));
});
inject(function($compile, $rootScope, $templateCache) {
$templateCache.put('template.html', '<a xlink:href="{{linkurl}}">{{text}}</a>');
element = $compile('<svg><g svg-anchor="/foo/bar" text="foo/bar!"></g></svg>')($rootScope);
$rootScope.$digest();
var child = element.children().eq(0);
expect(nodeName_(child)).toMatch(/a/i);
expect(child[0].constructor).toBe(window.SVGAElement);
expect(child[0].href.baseVal).toBe("/foo/bar");
});
});
}

// MathML is only natively supported in Firefox at the time of this test's writing,
// and even there, the browser does not export MathML element constructors globally.
// So the test is slightly limited in what it does. But as browsers begin to
// implement MathML natively, this can be tightened up to be more meaningful.
it('should support MathML templates using directive.type=math', function() {
module(function() {
directive('pow', valueFn({
replace: true,
transclude: true,
templateUrl: 'template.html',
type: 'MATH',
scope: {
pow: '@pow',
},
link: function(scope, elm, attr, ctrl, transclude) {
transclude(function(node) {
elm.prepend(node[0]);
});
}
}));
});
inject(function($compile, $rootScope, $templateCache) {
$templateCache.put('template.html', '<msup><mn>{{pow}}</mn></msup>');
element = $compile('<math><mn pow="2"><mn>8</mn></mn></math>')($rootScope);
$rootScope.$digest();
var child = element.children().eq(0);
expect(nodeName_(child)).toMatch(/msup/i);
});
});
});


Expand Down

0 comments on commit f0e12ea

Please sign in to comment.