Skip to content

Commit 1036ac1

Browse files
authored
adding a mutationobserver to pfe-tabs (#402)
* adding a mutationobserver to pfe-tabs Adding a mutation observer so the component will continue working properly if new tabs and panels are added. Fixes #332 * forcing the role to be tablist
1 parent 73f9169 commit 1036ac1

File tree

5 files changed

+197
-47
lines changed

5 files changed

+197
-47
lines changed

elements/pfe-tabs/demo/index.html

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252

5353
<link href="../../pfelement/pfelement.min.css" rel="stylesheet">
5454
</head>
55-
<body>
55+
<body unresolved>
5656
<h1>&lt;pfe-tabs&gt;</h1>
5757
<section class="example" style="background-color: #000;">
5858
<h2 style="color: #fff;">Style: Horizontal earth (on dark background)</h2>
@@ -221,5 +221,44 @@ <h2>Content 3</h2>
221221
</pfe-tab-panel>
222222
</pfe-tabs>
223223
</section>
224+
<section class="example">
225+
<h2>Dynamically Adding a Tab and Tab Panel</h2>
226+
<pfe-tabs id="dynamic">
227+
<pfe-tab role="heading" slot="tab">Tab 1</pfe-tab>
228+
<pfe-tab-panel role="region" slot="panel">
229+
<h4>Tab 1</h4>
230+
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
231+
</pfe-tab-panel>
232+
<pfe-tab role="heading" slot="tab">Tab 2</pfe-tab>
233+
<pfe-tab-panel role="region" slot="panel">
234+
<h4>Tab 2</h4>
235+
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
236+
</pfe-tab-panel>
237+
</pfe-tabs>
238+
<button id="addTabAndPanelBtn">Add a Tab and Tab Panel</button>
239+
</section>
240+
241+
<script>
242+
var btn = document.querySelector("#addTabAndPanelBtn");
243+
var pfeTabs = document.querySelector("#dynamic");
244+
245+
btn.addEventListener("click", function () {
246+
var fragment = document.createDocumentFragment();
247+
248+
var tab = document.createElement("pfe-tab");
249+
tab.setAttribute("role", "heading");
250+
tab.setAttribute("slot", "tab");
251+
tab.textContent = "New Tab";
252+
253+
var panel = document.createElement("pfe-tab-panel");
254+
panel.setAttribute("role", "region");
255+
panel.setAttribute("slot", "panel");
256+
panel.innerHTML = "<p>New tab panel</p>";
257+
258+
fragment.appendChild(tab);
259+
fragment.appendChild(panel);
260+
pfeTabs.appendChild(fragment);
261+
});
262+
</script>
224263
</body>
225264
</html>

elements/pfe-tabs/package-lock.json

Lines changed: 7 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

elements/pfe-tabs/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@
1515
],
1616
"repository": {
1717
"type": "git",
18-
"url" : "github:patternfly/patternfly-elements",
18+
"url": "github:patternfly/patternfly-elements",
1919
"directory": "elements/pfe-tabs"
2020
},
2121
"main": "pfe-tabs.js",
2222
"scripts": {
2323
"build": "../../node_modules/.bin/gulp && ../../node_modules/.bin/prettier --ignore-path ../../.prettierignore --write '**/*.{js,json}'",
2424
"dev": "../../node_modules/.bin/gulp dev",
25-
"test": "../../node_modules/.bin/wct --configFile ../../wct.conf.json node_modules/@patternfly/pfe-tabs/test/"
25+
"test": "../../node_modules/.bin/wct --configFile ../../wct.conf.json elements/pfe-tabs/test/"
2626
},
2727
"contributors": [
2828
{

elements/pfe-tabs/src/pfe-tabs.js

Lines changed: 105 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -142,15 +142,10 @@ class PfeTabs extends PFElement {
142142
super(PfeTabs);
143143

144144
this._linked = false;
145-
146-
this._onSlotChange = this._onSlotChange.bind(this);
145+
this._init = this._init.bind(this);
147146
this._onClick = this._onClick.bind(this);
148-
149-
this._tabSlot = this.shadowRoot.querySelector('slot[name="tab"]');
150-
this._panelSlot = this.shadowRoot.querySelector('slot[name="panel"]');
151-
152-
this._tabSlot.addEventListener("slotchange", this._onSlotChange);
153-
this._panelSlot.addEventListener("slotchange", this._onSlotChange);
147+
this._linkPanels = this._linkPanels.bind(this);
148+
this._observer = new MutationObserver(this._init);
154149
}
155150

156151
connectedCallback() {
@@ -159,23 +154,22 @@ class PfeTabs extends PFElement {
159154
this.addEventListener("keydown", this._onKeyDown);
160155
this.addEventListener("click", this._onClick);
161156

162-
if (!this.hasAttribute("role")) {
163-
this.setAttribute("role", "tablist");
164-
}
165-
166-
if (!this.hasAttribute("selected-index")) {
167-
this.selectedIndex = 0;
168-
}
169-
170157
Promise.all([
171158
customElements.whenDefined(PfeTab.tag),
172159
customElements.whenDefined(PfeTabPanel.tag)
173-
]).then(() => this._linkPanels());
160+
]).then(() => {
161+
if (this.children.length) {
162+
this._init();
163+
}
164+
165+
this._observer.observe(this, { childList: true });
166+
});
174167
}
175168

176169
disconnectedCallback() {
177170
this.removeEventListener("keydown", this._onKeyDown);
178-
this._allTabs().forEach(tab => tab.removeEventListener("click", this._onClick))
171+
this._allTabs().forEach(tab => tab.removeEventListener("click", this._onClick));
172+
this._observer.disconnect();
179173
}
180174

181175
attributeChangedCallback(attr, oldValue, newValue) {
@@ -264,7 +258,15 @@ class PfeTabs extends PFElement {
264258
this._selectTab(tab);
265259
}
266260

267-
_onSlotChange() {
261+
_init() {
262+
if (this.getAttribute("role") !== "tablist") {
263+
this.setAttribute("role", "tablist");
264+
}
265+
266+
if (!this.hasAttribute("selected-index")) {
267+
this.selectedIndex = 0;
268+
}
269+
268270
this._linked = false;
269271
this._linkPanels();
270272
}
@@ -274,6 +276,10 @@ class PfeTabs extends PFElement {
274276
return;
275277
}
276278

279+
if (window.ShadyCSS) {
280+
this._observer.disconnect();
281+
}
282+
277283
const tabs = this._allTabs();
278284

279285
tabs.forEach(tab => {
@@ -292,6 +298,10 @@ class PfeTabs extends PFElement {
292298
});
293299

294300
this._linked = true;
301+
302+
if (window.ShadyCSS) {
303+
this._observer.observe(this, { childList: true });
304+
}
295305
}
296306

297307
_allPanels() {
@@ -457,8 +467,13 @@ class PfeTab extends PFElement {
457467
return ["aria-selected"];
458468
}
459469

460-
constructor() {
461-
super(PfeTab);
470+
set selected(value) {
471+
value = Boolean(value);
472+
this.setAttribute("aria-selected", value);
473+
}
474+
475+
get selected() {
476+
return this.getAttribute("aria-selected") === "true" ? true : false;
462477
}
463478

464479
get pfeId() {
@@ -473,34 +488,60 @@ class PfeTab extends PFElement {
473488
this.setAttribute("pfe-id", id);
474489
}
475490

491+
constructor() {
492+
super(PfeTab);
493+
494+
this._init = this._init.bind(this);
495+
this._observer = new MutationObserver(this._init);
496+
}
497+
476498
connectedCallback() {
477499
super.connectedCallback();
478500

479-
if (!this.pfeId) {
480-
this.pfeId = `${PfeTab.tag}-${generateId()}`;
501+
if (this.children.length || this.textContent.trim().length) {
502+
this._init();
481503
}
482504

483-
this.setAttribute("role", "tab");
484-
this.setAttribute("aria-selected", "false");
485-
this.setAttribute("tabindex", -1);
486-
487-
if (this.parentNode.hasAttribute("vertical")) {
488-
this.setAttribute("vertical", "");
489-
}
505+
this._observer.observe(this, { childList: true });
490506
}
491507

492508
attributeChangedCallback() {
493509
const value = Boolean(this.selected);
494510
this.setAttribute("tabindex", value ? 0 : -1);
495511
}
496512

497-
set selected(value) {
498-
value = Boolean(value);
499-
this.setAttribute("aria-selected", value);
513+
disconnectedCallback() {
514+
this._observer.disconnect();
500515
}
501516

502-
get selected() {
503-
return this.getAttribute("aria-selected") === "true" ? true : false;
517+
_init() {
518+
if (window.ShadyCSS) {
519+
this._observer.disconnect();
520+
}
521+
522+
if (!this.pfeId) {
523+
this.pfeId = `${PfeTab.tag}-${generateId()}`;
524+
}
525+
526+
if (this.getAttribute("role") !== "tab") {
527+
this.setAttribute("role", "tab");
528+
}
529+
530+
if (!this.hasAttribute("aria-selected")) {
531+
this.setAttribute("aria-selected", "false");
532+
}
533+
534+
if (!this.hasAttribute("tabindex")) {
535+
this.setAttribute("tabindex", -1);
536+
}
537+
538+
if (this.parentNode.hasAttribute("vertical")) {
539+
this.setAttribute("vertical", "");
540+
}
541+
542+
if (window.ShadyCSS) {
543+
this._observer.observe(this, { childList: true });
544+
}
504545
}
505546
}
506547

@@ -531,17 +572,44 @@ class PfeTabPanel extends PFElement {
531572

532573
constructor() {
533574
super(PfeTabPanel);
575+
576+
this._init = this._init.bind(this);
577+
this._observer = new MutationObserver(this._init);
534578
}
535579

536580
connectedCallback() {
537581
super.connectedCallback();
538582

583+
this._init();
584+
this._observer.observe(this, { childList: true });
585+
}
586+
587+
disconnectedCallback() {
588+
this._observer.disconnect();
589+
}
590+
591+
_init() {
592+
if (window.ShadyCSS) {
593+
this._observer.disconnect();
594+
}
595+
539596
if (!this.pfeId) {
540597
this.pfeId = `${PfeTabPanel.tag}-${generateId()}`;
541598
}
542599

543-
this.setAttribute("role", "tabpanel");
544-
this.setAttribute("tabindex", 0);
600+
if (this.getAttribute("role") !== "tabpanel") {
601+
this.setAttribute("role", "tabpanel");
602+
}
603+
604+
if (!this.hasAttribute("tabindex")) {
605+
this.setAttribute("tabindex", 0);
606+
}
607+
608+
this.hidden = true;
609+
610+
if (window.ShadyCSS) {
611+
this._observer.observe(this, { childList: true });
612+
}
545613
}
546614
}
547615

elements/pfe-tabs/test/pfe-tabs_test.html

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,10 @@ <h2>Content 3</h2>
9797
</pfe-tab-panel>
9898
</pfe-tabs>
9999

100+
<pfe-tabs id="dynamic">
101+
<pfe-tab role="heading" slot="tab">Tab 1</pfe-tab>
102+
<pfe-tab-panel role="region" slot="panel">Tab 1 Content</pfe-tab-panel>
103+
</pfe-tabs>
100104
<script>
101105
suite('<pfe-tabs>', () => {
102106
setup(() => {
@@ -273,6 +277,45 @@ <h2>Content 3</h2>
273277
assert.equal(secondTab.getAttribute('aria-selected'), 'true');
274278
assert.isTrue(!secondPanel.hasAttribute('hidden'));
275279
});
280+
281+
test('it should properly initialize any dynamically added tabs and panels', done => {
282+
const pfeAccordion = document.querySelector('#dynamic');
283+
const documentFragment = document.createDocumentFragment();
284+
285+
const newTab = document.createElement("pfe-tab");
286+
newTab.id = "newTab";
287+
newTab.setAttribute("role", "heading");
288+
newTab.setAttribute("slot", "tab");
289+
newTab.textContent = `New Tab`;
290+
291+
const newPanel = document.createElement("pfe-tab-panel");
292+
newPanel.id = "newPanel"
293+
newPanel.setAttribute("role", "region");
294+
newPanel.setAttribute("slot", "panel");
295+
newPanel.textContent = `New Panel`;
296+
297+
documentFragment.appendChild(newTab);
298+
documentFragment.appendChild(newPanel);
299+
pfeAccordion.appendChild(documentFragment);
300+
301+
flush(() => {
302+
const newTabElement = document.querySelector("#newTab");
303+
const newPanelElement = document.querySelector("#newPanel");
304+
305+
assert.equal(newTabElement.getAttribute("role"), "tab");
306+
assert.isTrue(newTabElement.hasAttribute("pfe-id"));
307+
assert.isTrue(newTabElement.hasAttribute("aria-controls"));
308+
assert.equal(newTabElement.getAttribute("aria-controls"), newPanelElement.getAttribute("pfe-id"));
309+
310+
assert.equal(newPanelElement.getAttribute("role"), "tabpanel");
311+
assert.isTrue(newPanelElement.hasAttribute("pfe-id"));
312+
assert.isTrue(newPanelElement.hasAttribute("aria-labelledby"));
313+
assert.isTrue(newPanelElement.hasAttribute("hidden"));
314+
assert.equal(newPanelElement.getAttribute("aria-labelledby"), newTabElement.getAttribute("pfe-id"));
315+
316+
done();
317+
});
318+
});
276319
});
277320
</script>
278321
</body>

0 commit comments

Comments
 (0)