Skip to content

Commit 9559f41

Browse files
committed
fix(jqLite): make class API work consistently for IE9
IE9 has issues with setAttribute() and getAttribute() when an element uses XML namespaces. This should make the behaviour on IE9 consistent with a minimal perf hit, and make it easy to remove support when IE9 is no longer supported. Fixes angular#5001
1 parent c7a9009 commit 9559f41

File tree

2 files changed

+85
-2
lines changed

2 files changed

+85
-2
lines changed

src/jqLite.js

+54-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
/* global JQLitePrototype: true,
44
addEventListenerFn: true,
55
removeEventListenerFn: true,
6+
jqLiteAddClass: true,
7+
jqLiteRemoveClass: true,
68
BOOLEAN_ATTR: true,
79
ALIASED_ATTR: true,
810
*/
@@ -357,7 +359,10 @@ function jqLiteHasClass(element, selector) {
357359
indexOf( " " + selector + " " ) > -1);
358360
}
359361

360-
function jqLiteRemoveClass(element, cssClasses) {
362+
var jqLiteAddClass = msie === 9 ? jqLiteAddClassIE9 : jqLiteAddClassCommon;
363+
var jqLiteRemoveClass = msie === 9 ? jqLiteRemoveClassIE9 : jqLiteRemoveClassCommon;
364+
365+
function jqLiteRemoveClassCommon(element, cssClasses) {
361366
if (cssClasses && element.setAttribute) {
362367
forEach(cssClasses.split(' '), function(cssClass) {
363368
element.setAttribute('class', trim(
@@ -369,7 +374,29 @@ function jqLiteRemoveClass(element, cssClasses) {
369374
}
370375
}
371376

372-
function jqLiteAddClass(element, cssClasses) {
377+
function jqLiteRemoveClassIE9(element, cssClasses) {
378+
if (cssClasses && (element.className || element.className === '')) {
379+
var existingClasses = (' ' + (element.className.baseVal || element.className || '') + ' ')
380+
.replace(/[\r\n\s]+/g, ' ');
381+
382+
forEach(cssClasses.split(' '), function(cssClass) {
383+
cssClass = trim(cssClass);
384+
existingClasses = existingClasses.replace(' ' + cssClass + ' ', ' ');
385+
});
386+
387+
existingClasses = trim(existingClasses);
388+
389+
if (typeof element.className === 'string') {
390+
/* assuming regular node */
391+
element.className = existingClasses;
392+
} else {
393+
/* assuming SVG */
394+
element.className.baseVal = existingClasses;
395+
}
396+
}
397+
}
398+
399+
function jqLiteAddClassCommon(element, cssClasses) {
373400
if (cssClasses && element.setAttribute) {
374401
var existingClasses = (' ' + (element.getAttribute('class') || '') + ' ')
375402
.replace(/[\n\t]/g, " ");
@@ -385,6 +412,31 @@ function jqLiteAddClass(element, cssClasses) {
385412
}
386413
}
387414

415+
function jqLiteAddClassIE9(element, cssClasses) {
416+
// MSIE9 cannot reliably use setAttribute() when XML namespaces are used anywhere
417+
// on the element. Because of this, we work with className directly.
418+
if (cssClasses && (element.className || element.className === '')) {
419+
var existingClasses = (' ' + (element.className.baseVal || element.className || '') + ' ')
420+
.replace(/[\r\n\s]+/g, ' ');
421+
422+
forEach(cssClasses.split(' '), function(cssClass) {
423+
cssClass = trim(cssClass);
424+
if (existingClasses.indexOf(' ' + cssClass + ' ') === -1) {
425+
existingClasses += cssClass + ' ';
426+
}
427+
});
428+
existingClasses = trim(existingClasses);
429+
430+
if (typeof element.className === 'string') {
431+
/* assuming regular node */
432+
element.className = existingClasses;
433+
} else {
434+
/* assuming SVG */
435+
element.className.baseVal = existingClasses;
436+
}
437+
}
438+
}
439+
388440

389441
function jqLiteAddNodes(root, elements) {
390442
// THIS CODE IS VERY HOT. Don't make changes without benchmarking.

test/jqLiteSpec.js

+31
Original file line numberDiff line numberDiff line change
@@ -663,6 +663,37 @@ describe('jqLite', function() {
663663
});
664664

665665

666+
it('should properly work with elements with XML namespaced attributes', function() {
667+
var element = jqLite('<div ng:class="foo bar baz"></div>');
668+
669+
element.addClass('foo');
670+
element.addClass('bar');
671+
expect(element.hasClass('foo')).toBe(true);
672+
expect(element.hasClass('bar')).toBe(true);
673+
674+
element.removeClass('foo');
675+
expect(element.hasClass('foo')).toBe(false);
676+
expect(elemnet.hasClass('bar')).toBe(true);
677+
});
678+
679+
680+
it('should properly work with elements with XML namespaced attributes for SVG', function() {
681+
// this is a jqLite & SVG only test (jquery doesn't behave this way right now, which is a bug)
682+
if (!window.SVGElement || !_jqLiteMode) return;
683+
var svg = jqLite('<svg><rect ng:class="foo bar baz"></rect></svg>');
684+
var element = svg.children();
685+
686+
element.addClass('foo');
687+
element.addClass('bar');
688+
expect(element.hasClass('foo')).toBe(true);
689+
expect(element.hasClass('bar')).toBe(true);
690+
691+
element.removeClass('foo');
692+
expect(element.hasClass('foo')).toBe(false);
693+
expect(elemnet.hasClass('bar')).toBe(true);
694+
});
695+
696+
666697
it('should ignore comment elements', function() {
667698
var comment = jqLite(document.createComment('something'));
668699

0 commit comments

Comments
 (0)