Skip to content

Commit dd63cf9

Browse files
liuruenshengkatsev
authored andcommitted
fix: remove child from old parent when moving to new parent via addChild (videojs#5702)
A child component may have been assigned to another parent before assigning that child component to the new parent via "addChild" method. In this case, the original parent should remove the child then it can be safely added back to the new parent. This commit will keep the parent's "children_" and its DOM element's child nodes in the consistent state.
1 parent f02fb1b commit dd63cf9

File tree

3 files changed

+64
-0
lines changed

3 files changed

+64
-0
lines changed

src/js/component.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ class Component {
5858
this.player_ = player;
5959
}
6060

61+
// Hold the reference to the parent component via `addChild` method
62+
this.parentComponent_ = null;
63+
6164
// Make a copy of prototype.options_ to protect against overriding defaults
6265
this.options_ = mergeOptions({}, this.options_);
6366

@@ -142,6 +145,8 @@ class Component {
142145
this.childIndex_ = null;
143146
this.childNameIndex_ = null;
144147

148+
this.parentComponent_ = null;
149+
145150
if (this.el_) {
146151
// Remove element from DOM
147152
if (this.el_.parentNode) {
@@ -416,7 +421,11 @@ class Component {
416421
component = child;
417422
}
418423

424+
if (component.parentComponent_) {
425+
component.parentComponent_.removeChild(component);
426+
}
419427
this.children_.splice(index, 0, component);
428+
component.parentComponent_ = this;
420429

421430
if (typeof component.id === 'function') {
422431
this.childIndex_[component.id()] = component;
@@ -473,6 +482,8 @@ class Component {
473482
return;
474483
}
475484

485+
component.parentComponent_ = null;
486+
476487
this.childIndex_[component.id()] = null;
477488
this.childNameIndex_[component.name()] = null;
478489

test/unit/component.test.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1038,3 +1038,25 @@ QUnit.test('should use the stateful mixin', function(assert) {
10381038

10391039
comp.dispose();
10401040
});
1041+
1042+
QUnit.test('should remove child when the child moves to the other parent', function(assert) {
1043+
const parentComponent1 = new Component(getFakePlayer(), {});
1044+
const parentComponent2 = new Component(getFakePlayer(), {});
1045+
const childComponent = new Component(getFakePlayer(), {});
1046+
1047+
parentComponent1.addChild(childComponent);
1048+
1049+
assert.strictEqual(parentComponent1.children().length, 1, 'the children number of `parentComponent1` is 1');
1050+
assert.strictEqual(parentComponent1.children()[0], childComponent, 'the first child of `parentComponent1` is `childComponent`');
1051+
assert.strictEqual(parentComponent1.el().childNodes[0], childComponent.el(), '`parentComponent1` contains the DOM element of `childComponent`');
1052+
1053+
parentComponent2.addChild(childComponent);
1054+
1055+
assert.strictEqual(parentComponent1.children().length, 0, 'the children number of `parentComponent1` is 0');
1056+
assert.strictEqual(parentComponent1.el().childNodes.length, 0, 'the length of `childNodes` of `parentComponent1` is 0');
1057+
1058+
assert.strictEqual(parentComponent2.children().length, 1, 'the children number of `parentComponent2` is 1');
1059+
assert.strictEqual(parentComponent2.children()[0], childComponent, 'the first child of `parentComponent2` is `childComponent`');
1060+
assert.strictEqual(parentComponent2.el().childNodes.length, 1, 'the length of `childNodes` of `parentComponent2` is 1');
1061+
assert.strictEqual(parentComponent2.el().childNodes[0], childComponent.el(), '`parentComponent2` contains the DOM element of `childComponent`');
1062+
});

test/unit/menu.test.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/* eslint-env qunit */
22
import MenuButton from '../../src/js/menu/menu-button.js';
3+
import MenuItem from '../../src/js/menu/menu-item.js';
34
import TestHelpers from './test-helpers.js';
45
import * as Events from '../../src/js/utils/events.js';
56

@@ -75,3 +76,33 @@ QUnit.test('clicking should display the menu', function(assert) {
7576
menuButton.dispose();
7677
player.dispose();
7778
});
79+
80+
QUnit.test('should keep all the added menu items', function(assert) {
81+
const player = TestHelpers.makePlayer();
82+
83+
const menuItems = [];
84+
const menuItem1 = new MenuItem(player, { label: 'menu-item1' });
85+
const menuItem2 = new MenuItem(player, { label: 'menu-item2' });
86+
87+
MenuButton.prototype.createItems = function() {
88+
return menuItems;
89+
};
90+
91+
const menuButton = new MenuButton(player, {});
92+
93+
menuItems.push(menuItem1);
94+
menuButton.update();
95+
96+
assert.strictEqual(menuButton.children()[1].children().length, 1, 'the children number of the menu is 1 ');
97+
assert.strictEqual(menuButton.children()[1].children()[0], menuItem1, 'the first child of the menu is `menuItem1`');
98+
assert.ok(menuButton.el().contains(menuItem1.el()), 'the menu button contains the DOM element of `menuItem1`');
99+
100+
menuItems.push(menuItem2);
101+
menuButton.update();
102+
103+
assert.strictEqual(menuButton.children()[1].children().length, 2, 'the children number of the menu is 2 after second update');
104+
assert.strictEqual(menuButton.children()[1].children()[0], menuItem1, 'the first child of the menu is `menuItem1` after second update');
105+
assert.strictEqual(menuButton.children()[1].children()[1], menuItem2, 'the second child of the menu is `menuItem2` after second update');
106+
assert.ok(menuButton.el().contains(menuItem1.el()), 'the menu button contains the DOM element of `menuItem1` after second update');
107+
assert.ok(menuButton.el().contains(menuItem2.el()), 'the menu button contains the DOM element of `menuItem2` after second update');
108+
});

0 commit comments

Comments
 (0)