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

feat($compile): allow SVG and MathML templates via special type property #7265

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just an idea: This is not really wrapping, it's constructing DOM (in the case of SVG/Math). How about we make it to construct DOM from HTML even for regular templates and call it constructDOMfromHTML or somethin...

Anyway, just a comment, feel free to ignore this one...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm, if someone wants to come up with a better/more descriptive name for it, I don't have a problem with it. I think it's a good point.

My thinking is that it's (maybe) wrapping the template in a specific DOM node, so I called it wrapTemplate() --- but it is true that that's a bit of a misnomer.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

recastTemplate, maybe-perhaps? I know I haven't contributed as of yet, but I saw this in my inbox....

To Vojta's point, when I first read the line of code, I thought I would need to shed an enclosure . Reading the comments on the PR put me on the right track.

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 @@ -1604,6 +1657,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