More-or-Less panel (web component)

26 views
Skip to first unread message

luserdroog

unread,
Jul 6, 2023, 11:34:49 AMJul 6
to
I'm trying to factor out some of the disparate functionality in my app.
This one seemed to peel out rather nicely, only I'm somehow not able
to successfully send it a custom "redraw" event. In my application I'm
having to trigger a redraw by peeking into the shadow dom and calling
.click() twice on the toggle.

A <more-panel></more-panel> works like a div that can be in either
of two states: a 'more' state where the clickable word says "(less)"
and the panel hides contents with the class of "less" and shows
contents with the class of "more; and a 'less' state where the clickable
word says "(more)" and the panel hides contents with the class of
"more" and shows contents with the class of "less". Contents without
either of these classes is simply passed through unmolested.
Contents with both classes will be shown in both states.

The more-panel itself can be instantiated with or without the "more"
class to specify its initial state.

Any critiques or comments welcome.

more.js:
const morePanelTemplate = document.createElement("template");
morePanelTemplate.innerHTML = `
<style>
.content{
border: 1px solid;
border-radius: 5px;
}
#toggle{
font-size: small;
text-decoration: underline;
cursor: pointer;
}
</style>
<div class="content">
<span id=toggle></span>
<slot></slot>
</div>
`;

class MorePanel extends HTMLElement {

constructor() {
super();
this.attachShadow( {mode:"open"} );
this.shadowRoot.appendChild( morePanelTemplate.content.cloneNode(true) );
let content = this.shadowRoot.querySelector(".content");
this.showStuff();
}

showStuff() {
if( this.classList.contains( "more" ) )
this.showMore();
else
this.showLess();
}

connectedCallback() {
this.shadowRoot.querySelector("#toggle").addEventListener( "click", ()=>{
this.classList.toggle( "more" );
this.showStuff();
});
self.addEventListener( "redraw", (event) => this.showStuff() );
}

disconnectedCallback() {
this.shadowRoot.querySelector("#toggle").removeEventListener();
}

showMore() {
this.shadowRoot.querySelector("#toggle").textContent="(less)";
this.querySelectorAll(".less").forEach( el => el.style.display = 'none' );
this.querySelectorAll(".more").forEach( el => el.style.display = '' );
}

showLess() {
this.shadowRoot.querySelector("#toggle").textContent="(more)";
this.querySelectorAll(".more").forEach( el => el.style.display = 'none' );
this.querySelectorAll(".less").forEach( el => el.style.display = '' );
}
}

window.customElements.define( "more-panel", MorePanel );



more.html:
<!DOCTYPE html>
<meta http-equiv="content-type" content="text/html" charset="UTF-8">
<meta name="viewport" content="width=device-width">
<html>
<title></title>
<style>
</style>
<body>
<more-panel class="more">
<div class="more">This div</div>
<span>the span</span>
</more-panel>
</body>
<script src="more.js"></script>
<script>
</script>
</html>

Michael Haufe (TNO)

unread,
Jul 10, 2023, 1:46:36 PMJul 10
to
On Thursday, July 6, 2023 at 10:34:49 AM UTC-5, luserdroog wrote:
> I'm trying to factor out some of the disparate functionality in my app.
> This one seemed to peel out rather nicely, only I'm somehow not able
> to successfully send it a custom "redraw" event. In my application I'm
> having to trigger a redraw by peeking into the shadow dom and calling
> .click() twice on the toggle.
>
> A <more-panel></more-panel> works like a div that can be in either
> of two states: a 'more' state where the clickable word says "(less)"
> and the panel hides contents with the class of "less" and shows
> contents with the class of "more; and a 'less' state where the clickable
> word says "(more)" and the panel hides contents with the class of
> "more" and shows contents with the class of "less". Contents without
> either of these classes is simply passed through unmolested.
> Contents with both classes will be shown in both states.
>
> The more-panel itself can be instantiated with or without the "more"
> class to specify its initial state.
>
> Any critiques or comments welcome.
> [...]

There is a native <details /> tag you can use now:

<https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details>

Just style it as appropriate

luserdroog

unread,
Jul 10, 2023, 10:12:05 PMJul 10
to
Well, dang. I wish I'd noticed that earlier. Thanks. That does 90% of what
I wanted. Ultimately, I found the extra flexibility very useful. Being able to
toggle the "more" and "less" classes separately on the contents means
I can pop up an important control into the abbreviated state by just
adding "less" to it. I suppose I could accomplish similar with some js
gymnastics upon a <details />.

Anyway, good to know. I like mine better.

Michael Haufe (TNO)

unread,
Jul 11, 2023, 1:12:17 AMJul 11
to
On Monday, July 10, 2023 at 9:12:05 PM UTC-5, luserdroog wrote:
> > Just style it as appropriate
> Well, dang. I wish I'd noticed that earlier. Thanks. That does 90% of what
> I wanted. Ultimately, I found the extra flexibility very useful. Being able to
> toggle the "more" and "less" classes separately on the contents means
> I can pop up an important control into the abbreviated state by just
> adding "less" to it. I suppose I could accomplish similar with some js
> gymnastics upon a <details />.
>
> Anyway, good to know. I like mine better.

Well, the open state of <details /> can be styled with: details[open].

The JavaScript event is simply:

details.addEventListener("toggle", (event) => {
if (details.open) {
/* the element was toggled open */
} else {
/* the element was toggled closed */
}
});

luserdroog

unread,
Aug 9, 2023, 12:19:43 AMAug 9
to
This tidbit turned out incredibly valuable. I wanted to wrap them all up
and disappear them with one click. But mine don't nest. sigh.
Wrapping it all in a <details> got me out of the corner without stepping
in the paint.
Reply all
Reply to author
Forward
0 new messages