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

feat($compile): support transcluding SVG nodes when there is no root <svg> node #7384

Closed
wants to merge 2 commits 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
1 change: 1 addition & 0 deletions src/.jshintrc
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
"isElement": false,
"makeMap": false,
"map": false,
"reduce": false,
"size": false,
"includes": false,
"indexOf": false,
Expand Down
34 changes: 34 additions & 0 deletions src/Angular.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
-isElement,
-makeMap,
-map,
-reduce,
-size,
-includes,
-indexOf,
Expand Down Expand Up @@ -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
Expand Down
35 changes: 35 additions & 0 deletions src/ng/compile.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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>' + reduce($linkNode, function(previous, current) {
return previous + current.outerHTML;
}, '') + '</svg>';
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;
};
Expand Down
8 changes: 8 additions & 0 deletions test/helpers/matchers.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
31 changes: 31 additions & 0 deletions test/ng/compileSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: '<svg><g></g></svg>',
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('<div svg-root><circle cx="10" cy="10" r="10" ng-click="foo=88"/></div>')($rootScope);
$rootScope.$digest();
var circle = element.find('circle');
expect(circle.length).toBe(1);
expect(circle[0].constructor).toBe(window.SVGCircleElement);

// Ensure that the directives are still compiled
browserTrigger(circle, 'click');
expect($rootScope.foo).toBe(88);
expect(circle).toHaveClass('test');
});
});
});


Expand Down