<expanding-list>
Customized built-in elements (MDN), source code (GitHub)
helpers.js 👉 JS 常用函數
node.firstChild - ⭐️ 注意:「任何空白」都會產生 #text node,例如:
<p> <span>
element.firstElementChild - ⭐️ 如果不想抓到 #text 或 #comment,可使用此屬性。
DOMTokenList.remove() - 可用在 elem.classList.remove()
這個範例完全使用 light DOM 的內容,並沒有用到 shadow DOM。
/*
Customized built-in elements
----------------------------
https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements
Terms used in the following code
--------------------------------
• folder: <li> containing <ul>
• folder content: child <ul> (excluding host <ul>)
• folder title: <span> added by JS
*/
// customized built-in element
class ExpandingList extends HTMLUListElement {
constructor() {
// initialize HTMLUListElement
super();
// ⭐️ all folders
this.folders = Array
.from(this.$all('li'))
.filter(li => li.$all('ul').length > 0);
// for each folder
this.folders.forEach(li => {
// folder is closed by default
li.attr('class', 'closed');
// ⭐️ wrap <li>'s text in <span> in order to
// assign style and event handlers to the <span>
let textNode = li.firstChild; // `Text` node
const span = tag('span', {
textContent: textNode.textContent,
});
// decorate with Font Awesome (folder icon)
span.insertAdjacentHTML('afterbegin',
'<i class="fas fa-folder"></i>'
);
// add click handler
span.onclick = function(e){
const span = e.target; // folder title (<span>)
const li = span.parentNode; // folder (<li>)
const i = span.firstElementChild; // folder icon (<i>)
li.classList.toggle('closed'); // toggle folder status
// toggle folder icon
i.classList.toggle('fa-folder-open');
i.classList.toggle('fa-folder');
};
// prepend <span>, remove textNode
li.prepend(span);
textNode.remove();
});
}// end: constructor()
_openFolder(li){
// change folder status to open
li.classList.remove('closed');
// change to open folder icon
const i = li.$('span > i');
i.classList.add('fa-folder-open');
i.classList.remove('fa-folder');
}
// expand all folders
expandAll(){
this.folders.forEach(li => this._openFolder(li));
}
}// end: ExpandingList
// register <expanding-list>
customElements.define('expanding-list', ExpandingList, { extends: 'ul' });
<h3>DOM Hierarchy</h3>
<button onclick="list.expandAll()">Expand All</button>
<hr>
<ul is="expanding-list" id="list">
<!-- ⭐️ light DOM -->
<li>EventTarget
<ul>
<li>Node
<ul>
<li>DocumentFragment
<ul>
<li>ShadowRoot</li>
</ul>
</li>
<li>Element
<ul>
<li>HTMLElement</li>
<li>SVGElement</li>
</ul>
</li>
<li>CharacterData
<ul>
<li>Text</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
/* import Font Awesome */
@import url("https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@5.15.3/css/all.min.css");
/* for all icons */
.fas, .fab {
/* border: 1px solid black; */
font-size: 1rem;
/* background-color: white; */
color: hsl(5, 80%, 60%);
margin-right: 0.5rem;
}
/* closed icon */
.closed .fas, .closed .fab {
color: hsl(45, 80%, 60%);
}
/* for expanding-list light DOM */
ul {
list-style-type: none;
margin-left: 1.7rem;
padding-left: 0;
}
/* closed folder content invisible */
.closed > ul {
display: none;
}
/* hint to click */
span {
cursor: pointer;
}
/* highlight on hover */
span:hover {
background-color: hsla(60, 80%, 50%, 0.3);
padding: 0 4px;
border: 1px dotted black;
border-radius: 4px;
}
Last updated