Skip to content

Commit db9a119

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 db9a119

File tree

2 files changed

+96
-3
lines changed

2 files changed

+96
-3
lines changed

src/jqLite.js

+65-3
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
/* global JQLitePrototype: true,
44
addEventListenerFn: true,
55
removeEventListenerFn: true,
6+
jqLiteHasClass: true,
7+
jqLiteAddClass: true,
8+
jqLiteRemoveClass: true,
69
BOOLEAN_ATTR: true,
710
ALIASED_ATTR: true,
811
*/
@@ -351,13 +354,18 @@ function jqLiteData(element, key, value) {
351354
}
352355
}
353356

354-
function jqLiteHasClass(element, selector) {
357+
358+
var jqLiteHasClass = msie === 9 ? jqLiteHasClassIE9 : jqLiteHasClassCommon;
359+
var jqLiteAddClass = msie === 9 ? jqLiteAddClassIE9 : jqLiteAddClassCommon;
360+
var jqLiteRemoveClass = msie === 9 ? jqLiteRemoveClassIE9 : jqLiteRemoveClassCommon;
361+
362+
function jqLiteHasClassCommon(element, selector) {
355363
if (!element.getAttribute) return false;
356364
return ((" " + (element.getAttribute('class') || '') + " ").replace(/[\n\t]/g, " ").
357365
indexOf( " " + selector + " " ) > -1);
358366
}
359367

360-
function jqLiteRemoveClass(element, cssClasses) {
368+
function jqLiteRemoveClassCommon(element, cssClasses) {
361369
if (cssClasses && element.setAttribute) {
362370
forEach(cssClasses.split(' '), function(cssClass) {
363371
element.setAttribute('class', trim(
@@ -369,7 +377,7 @@ function jqLiteRemoveClass(element, cssClasses) {
369377
}
370378
}
371379

372-
function jqLiteAddClass(element, cssClasses) {
380+
function jqLiteAddClassCommon(element, cssClasses) {
373381
if (cssClasses && element.setAttribute) {
374382
var existingClasses = (' ' + (element.getAttribute('class') || '') + ' ')
375383
.replace(/[\n\t]/g, " ");
@@ -385,6 +393,60 @@ function jqLiteAddClass(element, cssClasses) {
385393
}
386394
}
387395

396+
function jqLiteHasClassIE9(element, selector) {
397+
if (!element.className && element.className !== '') return false;
398+
var existingClasses = (' ' + (element.className.baseVal || element.className || '') + ' ')
399+
.replace(/[\r\n\s]+/g, ' ');
400+
return existingClasses.indexOf( " " + selector + " " ) > -1);
401+
}
402+
403+
function jqLiteRemoveClassIE9(element, cssClasses) {
404+
if (cssClasses && (element.className || element.className === '')) {
405+
var existingClasses = (' ' + (element.className.baseVal || element.className || '') + ' ')
406+
.replace(/[\r\n\s]+/g, ' ');
407+
408+
forEach(cssClasses.split(' '), function(cssClass) {
409+
cssClass = trim(cssClass);
410+
existingClasses = existingClasses.replace(' ' + cssClass + ' ', ' ');
411+
});
412+
413+
existingClasses = trim(existingClasses);
414+
415+
if (typeof element.className === 'string') {
416+
/* assuming regular node */
417+
element.className = existingClasses;
418+
} else {
419+
/* assuming SVG */
420+
element.className.baseVal = existingClasses;
421+
}
422+
}
423+
}
424+
425+
function jqLiteAddClassIE9(element, cssClasses) {
426+
// MSIE9 cannot reliably use setAttribute() when XML namespaces are used anywhere
427+
// on the element. Because of this, we work with className directly.
428+
if (cssClasses && (element.className || element.className === '')) {
429+
var existingClasses = (' ' + (element.className.baseVal || element.className || '') + ' ')
430+
.replace(/[\r\n\s]+/g, ' ');
431+
432+
forEach(cssClasses.split(' '), function(cssClass) {
433+
cssClass = trim(cssClass);
434+
if (existingClasses.indexOf(' ' + cssClass + ' ') === -1) {
435+
existingClasses += cssClass + ' ';
436+
}
437+
});
438+
existingClasses = trim(existingClasses);
439+
440+
if (typeof element.className === 'string') {
441+
/* assuming regular node */
442+
element.className = existingClasses;
443+
} else {
444+
/* assuming SVG */
445+
element.className.baseVal = existingClasses;
446+
}
447+
}
448+
}
449+
388450

389451
function jqLiteAddNodes(root, elements) {
390452
// 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(element.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(element.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)