diff --git a/src/.jshintrc b/src/.jshintrc index 1202b64447a3..2f735cce6e10 100644 --- a/src/.jshintrc +++ b/src/.jshintrc @@ -70,6 +70,7 @@ "isElement": false, "makeMap": false, "map": false, + "reduce": false, "size": false, "includes": false, "indexOf": false, diff --git a/src/Angular.js b/src/Angular.js index 57f478b5b944..50ca116de1a7 100644 --- a/src/Angular.js +++ b/src/Angular.js @@ -50,6 +50,7 @@ -isElement, -makeMap, -map, + -reduce, -size, -includes, -indexOf, @@ -641,6 +642,39 @@ function map(obj, iterator, context) { } +/** + * @description + * Reduces a collection into a single accumulated value. Based on ES5 Array#reduce + * + * This is useful for performing operations on a collection, which should result in + * a single value, such as concatenating the outerHTML of a collection of DOM nodes + * into a single string. + * + * @param {Array} array The array to operate on. + * @param {Function(*, *, i, Array)} callback Count function to operate on each value in the + * collection. The parameters are as follows: + * - previousValue The previous accumulated value + * - currentValue The value from the current index in the collection + * - index The index of the current value + * - array A reference to the array being operated on + * + * The callback should return the accumulated value. + * @param {*} initialValue The initial value for the accumulator + * + * @returns {*} The accumulated value, resulting from callback being called on each element + * in the collection. See http://goo.gl/YwhaCh for more details. + */ +function reduce(array, callback, initialValue) { + var i; + var ii; + var previousValue = initialValue; + for (i = 0, ii = array.length; i < ii; ++i) { + previousValue = callback(previousValue, array[i], i, array); + } + return previousValue; +} + + /** * @description * Determines the number of elements in an array, the number of properties an object has, or diff --git a/src/ng/compile.js b/src/ng/compile.js index a5b49cb42d0a..0ac4b33aeadf 100644 --- a/src/ng/compile.js +++ b/src/ng/compile.js @@ -854,6 +854,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { safeAddClass($compileNodes, 'ng-scope'); return function publicLinkFn(scope, cloneConnectFn, transcludeControllers){ assertArg(scope, 'scope'); + // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart // and sometimes changes the structure of the DOM. var $linkNode = cloneConnectFn @@ -874,6 +875,40 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { } if (cloneConnectFn) cloneConnectFn($linkNode, scope); + + if (window.HTMLUnknownElement) { + // If any of these elements are HTMLUnknownElement, it may be necessary + // to fix them. + var _i, _ii; + for (_i = 0, _ii = $linkNode.length; _i < _ii; ++_i) { + if ($linkNode[_i].constructor === window.HTMLUnknownElement) { + var origParent = $linkNode[_i].parentNode; + var parent = origParent; + while (parent) { + if (window.SVGSVGElement && parent.constructor === window.SVGSVGElement) { + var wrapper = document.createElement('div'), svg; + wrapper.innerHTML = ''; + svg = wrapper.childNodes[0]; + $linkNode.remove(); + $linkNode.length = 0; + forEach(svg.childNodes, function(node) { + svg.removeChild(node); + $linkNode.push(node); + }); + + // Sadly, this needs to be re-linked ._. + if (cloneConnectFn) cloneConnectFn($linkNode, scope); + break; + } + parent = parent.parentNode; + } + break; + } + } + } + if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode); return $linkNode; }; diff --git a/test/helpers/matchers.js b/test/helpers/matchers.js index b18604bf9ba4..6ca789c27286 100644 --- a/test/helpers/matchers.js +++ b/test/helpers/matchers.js @@ -167,6 +167,14 @@ beforeEach(function() { this.message = function() { return "Expected '" + angular.mock.dump(this.actual) + "' to have class '" + clazz + "'."; }; + var node = this.actual; + if (node.hasClass) node = node[0]; + if (window.SVGElement && node instanceof window.SVGElement + && typeof node.className === "object") { + var tmp = document.createElement('div'); + tmp.className = node.className.animVal; + return jqLite(tmp).hasClass(clazz); + } return this.actual.hasClass ? this.actual.hasClass(clazz) : angular.element(this.actual).hasClass(clazz); diff --git a/test/ng/compileSpec.js b/test/ng/compileSpec.js index 6dcc536276a4..b10e35ae65cc 100755 --- a/test/ng/compileSpec.js +++ b/test/ng/compileSpec.js @@ -4040,6 +4040,37 @@ describe('$compile', function() { }); }); + + + it('should transclude SVG content into SVG templates', function() { + if (!window.SVGCircleElement) return; + module(function() { + directive('svgRoot', valueFn({ + restrict: 'A', + template: '', + replace: true, + transclude: true, + link: function(scope, elem, attr, ctrl, transclude) { + transclude(scope, function(nodes) { + jqLite(nodes).addClass('test'); + elem.children(0).append(nodes); + }); + } + })); + }); + inject(function($compile, $rootScope) { + element = $compile('