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

fix(jqLite): make removeData() not remove event handlers #16512

Merged
merged 1 commit into from
Apr 3, 2018
Merged
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
30 changes: 15 additions & 15 deletions src/Angular.js
Original file line number Diff line number Diff line change
Expand Up @@ -1924,25 +1924,25 @@ function bindJQuery() {
injector: JQLitePrototype.injector,
inheritedData: JQLitePrototype.inheritedData
});

// All nodes removed from the DOM via various jQuery APIs like .remove()
// are passed through jQuery.cleanData. Monkey-patch this method to fire
// the $destroy event on all removed nodes.
originalCleanData = jQuery.cleanData;
jQuery.cleanData = function(elems) {
var events;
for (var i = 0, elem; (elem = elems[i]) != null; i++) {
events = jQuery._data(elem, 'events');
if (events && events.$destroy) {
jQuery(elem).triggerHandler('$destroy');
}
}
originalCleanData(elems);
};
} else {
jqLite = JQLite;
}

// All nodes removed from the DOM via various jqLite/jQuery APIs like .remove()
// are passed through jqLite/jQuery.cleanData. Monkey-patch this method to fire
// the $destroy event on all removed nodes.
originalCleanData = jqLite.cleanData;
jqLite.cleanData = function(elems) {
var events;
for (var i = 0, elem; (elem = elems[i]) != null; i++) {
events = jqLite._data(elem).events;
if (events && events.$destroy) {
jqLite(elem).triggerHandler('$destroy');
}
}
originalCleanData(elems);
};

angular.element = jqLite;

// Prevent double-proxying.
Expand Down
37 changes: 28 additions & 9 deletions src/jqLite.js
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,28 @@ function jqLiteDealoc(element, onlyDescendants) {
}
}

function isEmptyObject(obj) {
var name;

for (name in obj) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really care about inherited properties here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return false;
}
return true;
}

function removeIfEmptyData(element) {
var expandoId = element.ng339;
var expandoStore = expandoId && jqCache[expandoId];

var events = expandoStore && expandoStore.events;
var data = expandoStore && expandoStore.data;

if ((!data || isEmptyObject(data)) && (!events || isEmptyObject(events))) {
delete jqCache[expandoId];
element.ng339 = undefined; // don't delete DOM expandos. IE and Chrome don't like it
}
}

function jqLiteOff(element, type, fn, unsupported) {
if (isDefined(unsupported)) throw jqLiteMinErr('offargs', 'jqLite#off() does not support the `selector` argument');

Expand Down Expand Up @@ -346,6 +368,8 @@ function jqLiteOff(element, type, fn, unsupported) {
}
});
}

removeIfEmptyData(element);
}

function jqLiteRemoveData(element, name) {
Expand All @@ -355,17 +379,11 @@ function jqLiteRemoveData(element, name) {
if (expandoStore) {
if (name) {
delete expandoStore.data[name];
return;
} else {
expandoStore.data = {};
}

if (expandoStore.handle) {
if (expandoStore.events.$destroy) {
expandoStore.handle({}, '$destroy');
}
jqLiteOff(element);
}
delete jqCache[expandoId];
element.ng339 = undefined; // don't delete DOM expandos. IE and Chrome don't like it
removeIfEmptyData(element);
}
}

Expand Down Expand Up @@ -615,6 +633,7 @@ forEach({
cleanData: function jqLiteCleanData(nodes) {
for (var i = 0, ii = nodes.length; i < ii; i++) {
jqLiteRemoveData(nodes[i]);
jqLiteOff(nodes[i]);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAICT, $destroy handlers will not be removed, which means that the expandoStore will never be completely empty and removed.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be handled in the cleanData patch that used to be applied to jQuery only and that I moved to apply to jqLite as well.

}
}
}, function(fn, name) {
Expand Down
81 changes: 81 additions & 0 deletions test/jqLiteSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,87 @@ describe('jqLite', function() {
selected.removeData('prop2');
});

it('should not remove event handlers on removeData()', function() {
var log = '';
var elm = jqLite(a);
elm.on('click', function() {
log += 'click;';
});

elm.removeData();
browserTrigger(a, 'click');
expect(log).toBe('click;');
});

it('should allow to set data after removeData() with event handlers present', function() {
var elm = jqLite(a);
elm.on('click', function() {});
elm.data('key1', 'value1');
elm.removeData();
elm.data('key2', 'value2');
expect(elm.data('key1')).not.toBeDefined();
expect(elm.data('key2')).toBe('value2');
});

it('should allow to set data after removeData() without event handlers present', function() {
var elm = jqLite(a);
elm.data('key1', 'value1');
elm.removeData();
elm.data('key2', 'value2');
expect(elm.data('key1')).not.toBeDefined();
expect(elm.data('key2')).toBe('value2');
});


it('should remove user data on cleanData()', function() {
var selected = jqLite([a, b, c]);

selected.data('prop', 'value');
jqLite(b).data('prop', 'new value');

jqLite.cleanData(selected);

expect(jqLite(a).data('prop')).toBeUndefined();
expect(jqLite(b).data('prop')).toBeUndefined();
expect(jqLite(c).data('prop')).toBeUndefined();
});

it('should remove event handlers on cleanData()', function() {
var selected = jqLite([a, b, c]);

var log = '';
var elm = jqLite(b);
elm.on('click', function() {
log += 'click;';
});
jqLite.cleanData(selected);

browserTrigger(b, 'click');
expect(log).toBe('');
});

it('should remove user data & event handlers on cleanData()', function() {
var selected = jqLite([a, b, c]);

var log = '';
var elm = jqLite(b);
elm.on('click', function() {
log += 'click;';
});

selected.data('prop', 'value');
jqLite(a).data('prop', 'new value');

jqLite.cleanData(selected);

browserTrigger(b, 'click');
expect(log).toBe('');

expect(jqLite(a).data('prop')).toBeUndefined();
expect(jqLite(b).data('prop')).toBeUndefined();
expect(jqLite(c).data('prop')).toBeUndefined();
});


it('should add and remove data on SVGs', function() {
var svg = jqLite('<svg><rect></rect></svg>');
Expand Down