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

Commit f0e12ea

Browse files
committed
feat($compile): allow SVG and MathML templates via special type property
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
1 parent 28ef263 commit f0e12ea

File tree

2 files changed

+139
-3
lines changed

2 files changed

+139
-3
lines changed

src/ng/compile.js

+31-3
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,19 @@
213213
* * `M` - Comment: `<!-- directive: my-directive exp -->`
214214
*
215215
*
216+
* #### `type`
217+
* String representing the document type used by the markup. This is useful for templates where the root
218+
* node is non-HTML content (such as SVG or MathML). The default value is "html".
219+
*
220+
* * `html` - All root template nodes are HTML, and don't need to be wrapped. Root nodes may also be
221+
* top-level elements such as `<svg>` or `<math>`.
222+
* * `svg` - The template contains only SVG content, and must be wrapped in an `<svg>` node prior to
223+
* processing.
224+
* * `math` - The template contains only MathML content, and must be wrapped in an `<math>` node prior to
225+
* processing.
226+
*
227+
* If no `type` is specified, then the type is considered to be html.
228+
*
216229
* #### `template`
217230
* replace the current element with the contents of the HTML. The replacement process
218231
* migrates all of the attributes / classes from the old element to the new one. See the
@@ -1261,7 +1274,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
12611274
if (jqLiteIsTextNode(directiveValue)) {
12621275
$template = [];
12631276
} else {
1264-
$template = jqLite(trim(directiveValue));
1277+
$template = jqLite(wrapTemplate(directive.type, trim(directiveValue)));
12651278
}
12661279
compileNode = $template[0];
12671280

@@ -1676,7 +1689,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
16761689
}),
16771690
templateUrl = (isFunction(origAsyncDirective.templateUrl))
16781691
? origAsyncDirective.templateUrl($compileNode, tAttrs)
1679-
: origAsyncDirective.templateUrl;
1692+
: origAsyncDirective.templateUrl,
1693+
type = origAsyncDirective.type;
16801694

16811695
$compileNode.empty();
16821696

@@ -1690,7 +1704,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
16901704
if (jqLiteIsTextNode(content)) {
16911705
$template = [];
16921706
} else {
1693-
$template = jqLite(trim(content));
1707+
$template = jqLite(wrapTemplate(type, trim(content)));
16941708
}
16951709
compileNode = $template[0];
16961710

@@ -1813,6 +1827,20 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
18131827
}
18141828

18151829

1830+
function wrapTemplate(type, template) {
1831+
type = lowercase(type || 'html');
1832+
switch(type) {
1833+
case 'svg':
1834+
case 'math':
1835+
var wrapper = document.createElement('div');
1836+
wrapper.innerHTML = '<'+type+'>'+template+'</'+type+'>';
1837+
return wrapper.childNodes[0].childNodes;
1838+
default:
1839+
return template;
1840+
}
1841+
}
1842+
1843+
18161844
function getTrustedContext(node, attrNormalizedName) {
18171845
if (attrNormalizedName == "srcdoc") {
18181846
return $sce.HTML;

test/ng/compileSpec.js

+108
Original file line numberDiff line numberDiff line change
@@ -768,6 +768,59 @@ describe('$compile', function() {
768768
}).not.toThrow();
769769
expect(nodeName_(element)).toMatch(/optgroup/i);
770770
}));
771+
772+
if (window.SVGAElement) {
773+
it('should support SVG templates using directive.type=svg', function() {
774+
module(function() {
775+
directive('svgAnchor', valueFn({
776+
replace: true,
777+
template: '<a xlink:href="{{linkurl}}">{{text}}</a>',
778+
type: 'SVG',
779+
scope: {
780+
linkurl: '@svgAnchor',
781+
text: '@?'
782+
}
783+
}));
784+
});
785+
inject(function($compile, $rootScope) {
786+
element = $compile('<svg><g svg-anchor="/foo/bar" text="foo/bar!"></g></svg>')($rootScope);
787+
var child = element.children().eq(0);
788+
$rootScope.$digest();
789+
expect(nodeName_(child)).toMatch(/a/i);
790+
expect(child[0].constructor).toBe(window.SVGAElement);
791+
expect(child[0].href.baseVal).toBe("/foo/bar");
792+
});
793+
});
794+
}
795+
796+
// MathML is only natively supported in Firefox at the time of this test's writing,
797+
// and even there, the browser does not export MathML element constructors globally.
798+
// So the test is slightly limited in what it does. But as browsers begin to
799+
// implement MathML natively, this can be tightened up to be more meaningful.
800+
it('should support MathML templates using directive.type=math', function() {
801+
module(function() {
802+
directive('pow', valueFn({
803+
replace: true,
804+
transclude: true,
805+
template: '<msup><mn>{{pow}}</mn></msup>',
806+
type: 'MATH',
807+
scope: {
808+
pow: '@pow',
809+
},
810+
link: function(scope, elm, attr, ctrl, transclude) {
811+
transclude(function(node) {
812+
elm.prepend(node[0]);
813+
});
814+
}
815+
}));
816+
});
817+
inject(function($compile, $rootScope) {
818+
element = $compile('<math><mn pow="2"><mn>8</mn></mn></math>')($rootScope);
819+
$rootScope.$digest();
820+
var child = element.children().eq(0);
821+
expect(nodeName_(child)).toMatch(/msup/i);
822+
});
823+
});
771824
});
772825

773826

@@ -1612,6 +1665,61 @@ describe('$compile', function() {
16121665
$rootScope.$digest();
16131666
expect(nodeName_(element)).toMatch(/optgroup/i);
16141667
}));
1668+
1669+
if (window.SVGAElement) {
1670+
it('should support SVG templates using directive.type=svg', function() {
1671+
module(function() {
1672+
directive('svgAnchor', valueFn({
1673+
replace: true,
1674+
templateUrl: 'template.html',
1675+
type: 'SVG',
1676+
scope: {
1677+
linkurl: '@svgAnchor',
1678+
text: '@?'
1679+
}
1680+
}));
1681+
});
1682+
inject(function($compile, $rootScope, $templateCache) {
1683+
$templateCache.put('template.html', '<a xlink:href="{{linkurl}}">{{text}}</a>');
1684+
element = $compile('<svg><g svg-anchor="/foo/bar" text="foo/bar!"></g></svg>')($rootScope);
1685+
$rootScope.$digest();
1686+
var child = element.children().eq(0);
1687+
expect(nodeName_(child)).toMatch(/a/i);
1688+
expect(child[0].constructor).toBe(window.SVGAElement);
1689+
expect(child[0].href.baseVal).toBe("/foo/bar");
1690+
});
1691+
});
1692+
}
1693+
1694+
// MathML is only natively supported in Firefox at the time of this test's writing,
1695+
// and even there, the browser does not export MathML element constructors globally.
1696+
// So the test is slightly limited in what it does. But as browsers begin to
1697+
// implement MathML natively, this can be tightened up to be more meaningful.
1698+
it('should support MathML templates using directive.type=math', function() {
1699+
module(function() {
1700+
directive('pow', valueFn({
1701+
replace: true,
1702+
transclude: true,
1703+
templateUrl: 'template.html',
1704+
type: 'MATH',
1705+
scope: {
1706+
pow: '@pow',
1707+
},
1708+
link: function(scope, elm, attr, ctrl, transclude) {
1709+
transclude(function(node) {
1710+
elm.prepend(node[0]);
1711+
});
1712+
}
1713+
}));
1714+
});
1715+
inject(function($compile, $rootScope, $templateCache) {
1716+
$templateCache.put('template.html', '<msup><mn>{{pow}}</mn></msup>');
1717+
element = $compile('<math><mn pow="2"><mn>8</mn></mn></math>')($rootScope);
1718+
$rootScope.$digest();
1719+
var child = element.children().eq(0);
1720+
expect(nodeName_(child)).toMatch(/msup/i);
1721+
});
1722+
});
16151723
});
16161724

16171725

0 commit comments

Comments
 (0)