diff --git a/src/jqLite.js b/src/jqLite.js index ad59fbd1af2b..f7913ac4312f 100644 --- a/src/jqLite.js +++ b/src/jqLite.js @@ -420,13 +420,18 @@ function jqLiteHasClass(element, selector) { function jqLiteRemoveClass(element, cssClasses) { if (cssClasses && element.setAttribute) { + var existingClasses = (' ' + (element.getAttribute('class') || '') + ' ') + .replace(/[\n\t]/g, ' '); + var newClasses = existingClasses; + forEach(cssClasses.split(' '), function(cssClass) { - element.setAttribute('class', trim( - (' ' + (element.getAttribute('class') || '') + ' ') - .replace(/[\n\t]/g, ' ') - .replace(' ' + trim(cssClass) + ' ', ' ')) - ); + cssClass = trim(cssClass); + newClasses = newClasses.replace(' ' + cssClass + ' ', ' '); }); + + if (newClasses !== existingClasses) { + element.setAttribute('class', trim(newClasses)); + } } } @@ -434,15 +439,18 @@ function jqLiteAddClass(element, cssClasses) { if (cssClasses && element.setAttribute) { var existingClasses = (' ' + (element.getAttribute('class') || '') + ' ') .replace(/[\n\t]/g, ' '); + var newClasses = existingClasses; forEach(cssClasses.split(' '), function(cssClass) { cssClass = trim(cssClass); - if (existingClasses.indexOf(' ' + cssClass + ' ') === -1) { - existingClasses += cssClass + ' '; + if (newClasses.indexOf(' ' + cssClass + ' ') === -1) { + newClasses += cssClass + ' '; } }); - element.setAttribute('class', trim(existingClasses)); + if (newClasses !== existingClasses) { + element.setAttribute('class', trim(newClasses)); + } } } diff --git a/test/jqLiteSpec.js b/test/jqLiteSpec.js index 60c9981e2429..8ca865d31112 100644 --- a/test/jqLiteSpec.js +++ b/test/jqLiteSpec.js @@ -914,6 +914,37 @@ describe('jqLite', function() { }); + // JQLite specific implementation/performance tests + if (_jqLiteMode) { + it('should only get/set the attribute once when multiple classes added', function() { + var fakeElement = { + nodeType: 1, + setAttribute: jasmine.createSpy(), + getAttribute: jasmine.createSpy().and.returnValue('') + }; + var jqA = jqLite(fakeElement); + + jqA.addClass('foo bar baz'); + expect(fakeElement.getAttribute).toHaveBeenCalledOnceWith('class'); + expect(fakeElement.setAttribute).toHaveBeenCalledOnceWith('class', 'foo bar baz'); + }); + + + it('should not set the attribute when classes not changed', function() { + var fakeElement = { + nodeType: 1, + setAttribute: jasmine.createSpy(), + getAttribute: jasmine.createSpy().and.returnValue('foo bar') + }; + var jqA = jqLite(fakeElement); + + jqA.addClass('foo'); + expect(fakeElement.getAttribute).toHaveBeenCalledOnceWith('class'); + expect(fakeElement.setAttribute).not.toHaveBeenCalled(); + }); + } + + it('should not add duplicate classes', function() { var jqA = jqLite(a); expect(a.className).toBe(''); @@ -1031,6 +1062,37 @@ describe('jqLite', function() { jqA.removeClass('foo baz noexistent'); expect(a.className).toBe('bar'); }); + + + // JQLite specific implementation/performance tests + if (_jqLiteMode) { + it('should get/set the attribute once when removing multiple classes', function() { + var fakeElement = { + nodeType: 1, + setAttribute: jasmine.createSpy(), + getAttribute: jasmine.createSpy().and.returnValue('foo bar baz') + }; + var jqA = jqLite(fakeElement); + + jqA.removeClass('foo baz noexistent'); + expect(fakeElement.getAttribute).toHaveBeenCalledOnceWith('class'); + expect(fakeElement.setAttribute).toHaveBeenCalledOnceWith('class', 'bar'); + }); + + + it('should not set the attribute when classes not changed', function() { + var fakeElement = { + nodeType: 1, + setAttribute: jasmine.createSpy(), + getAttribute: jasmine.createSpy().and.returnValue('foo bar') + }; + var jqA = jqLite(fakeElement); + + jqA.removeClass('noexistent'); + expect(fakeElement.getAttribute).toHaveBeenCalledOnceWith('class'); + expect(fakeElement.setAttribute).not.toHaveBeenCalled(); + }); + } }); });