diff --git a/src/ng/compile.js b/src/ng/compile.js index 4256adcaabc7..769ac0e0ff34 100644 --- a/src/ng/compile.js +++ b/src/ng/compile.js @@ -213,6 +213,19 @@ * * `M` - Comment: `` * * + * #### `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 `` or ``. + * * `svg` - The template contains only SVG content, and must be wrapped in an `` node prior to + * processing. + * * `math` - The template contains only MathML content, and must be wrapped in an `` 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 @@ -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]; @@ -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(); @@ -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]; @@ -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+''; + return wrapper.childNodes[0].childNodes; + default: + return template; + } + } + + function getTrustedContext(node, attrNormalizedName) { if (attrNormalizedName == "srcdoc") { return $sce.HTML; diff --git a/test/ng/compileSpec.js b/test/ng/compileSpec.js index 41bf4854b46d..6dcc536276a4 100755 --- a/test/ng/compileSpec.js +++ b/test/ng/compileSpec.js @@ -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: '{{text}}', + type: 'SVG', + scope: { + linkurl: '@svgAnchor', + text: '@?' + } + })); + }); + inject(function($compile, $rootScope) { + element = $compile('')($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: '{{pow}}', + 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('8')($rootScope); + $rootScope.$digest(); + var child = element.children().eq(0); + expect(nodeName_(child)).toMatch(/msup/i); + }); + }); }); @@ -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', '{{text}}'); + element = $compile('')($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', '{{pow}}'); + element = $compile('8')($rootScope); + $rootScope.$digest(); + var child = element.children().eq(0); + expect(nodeName_(child)).toMatch(/msup/i); + }); + }); });