Skip to content

Instantly share code, notes, and snippets.

@ceving
Last active April 21, 2024 09:46
Show Gist options
  • Star 32 You must be signed in to star a gist
  • Fork 6 You must be signed in to fork a gist
  • Save ceving/6e65886e04563ed9e6e42cc5f8d3f656 to your computer and use it in GitHub Desktop.
Save ceving/6e65886e04563ed9e6e42cc5f8d3f656 to your computer and use it in GitHub Desktop.
Web Component for Copyright Years
@Danny-Engelman
Copy link

Danny-Engelman commented Mar 28, 2024

  • You are not exporting anything, so no export required. Its a single anonymous call.

  • Apple for over 10 years now has stated they will never implement is= syntax (extending existing elements)

  • An Empty constructor call (only calling super()) is pointless , as each callback will call its parent callback by default; So if you do not declare a connectedCallback, the connectedCallback on the parent is called; I think they call it OOP

  • this.textContext only works for you because the component in this case is defined after DOM is parsed.
    Place it in the HEAD and not in a file that loads after DOMContentLoaded and your code will fail.

  • ::slotted() only works on DOM elements that get slotted, not on text elements

<copyright-year>Microsoft</copyright-year>
<copyright-year guri="guidelines URI">Apple</copyright-year>
<copyright-year fixed guri="guidelines URI">Meta</copyright-year>
<copyright-year fixed guri="guidelines URI"><b>Google</b></copyright-year>
<script>
  customElements.define("copyright-year", class extends HTMLElement {
    constructor() {
      const createElement = (tag, props = {}) => Object.assign(document.createElement(tag), props);
      const spacer = "&nbsp;".repeat(3);
      super()
        .attachShadow({ mode: "open" })
        .append(
            createElement("style", {
              innerHTML: 
              `:host{display:block;margin:0;padding:0;width:100%;background:beige}`+
              `:host(*){font-size:120%}`+
              `::slotted(*){font-size:120%;color:red}`+
              `:host([fixed]){display:inline-block;position:fixed;bottom:0}`
            }),
            createElement("span", {
              innerHTML: "©️ " + (new Date().getFullYear()) + spacer
            }),
            createElement("slot", {
              innerHTML: `by ACME`
            }),
            createElement("span",{
        	    innerHTML: this.getAttribute("guri") 
          	    ? spacer + `<A href="${this.getAttribute("guri")}">Read our guidelines</a>` 
                : ""
            })
          );
        }
    });

</script>

@Adnanear
Copy link

  • You are not exporting anything, so no export required. Its a single anonymous call.
  • Apple for over 10 years now has stated they will never implement is= syntax (extending existing elements)
  • An Empty constructor call (only calling super()) is pointless , as each callback will call its parent callback by default; So if you do not declare a connectedCallback, the connectedCallback on the parent is called; I think they call it OOP
  • this.textContext only workds becuase the component in this case is defined after DOM is parsed.
    Place it in the HEAD and not in a file that loads after DOMContentLoaded and your code will fail.
  • ::slotted() only works on DOM elements that get slotted, not on text elements
<copyright-year>Microsoft</copyright-year>
<copyright-year guri="guidelines URI">Apple</copyright-year>
<copyright-year fixed guri="guidelines URI">Meta</copyright-year>
<copyright-year fixed guri="guidelines URI"><b>Google</b></copyright-year>
<script>
  customElements.define("copyright-year", class extends HTMLElement {
    constructor() {
      const createElement = (tag, props = {}) => Object.assign(document.createElement(tag), props);
      const spacer = "&nbsp;".repeat(3);
      super()
        .attachShadow({
          mode: "open"
        })
      .append(
        createElement("style", {
          innerHTML: 
          `:host{display:block;margin:0;padding:0;width:100%;background:beige}`+
          `:host(*){font-size:120%}`+
          `::slotted(*){font-size:120%;color:red}`+
          `:host([fixed]){display:inline-block;position:fixed;bottom:0}`
        }),
        createElement("span", {
          innerHTML: "©️ " + (new Date().getFullYear()) + spacer
        }),
        createElement("slot", {
          innerHTML: `by ACME`
        }),
        createElement("span",{
        	innerHTML: this.getAttribute("guri") 
          	? spacer + `<A href="${this.getAttribute("guri")}">Read our guidelines</a>` 
            : ""
        })
      );
    }
  });

</script>

No one cares about Apple's safari to be honest, if the other major browsers decide to do something. Apple will be forced to join the union, otherwise it will be a downside for them.

It's not like Safari isn't the nowadays Internet Explorer, but it is indeed the most buggy one and the only one to make developers suffer in order to get things compatible without putting some kind of black sorcery just to make Apple happy -_-

@lukebrowell
Copy link

lukebrowell commented Mar 29, 2024

This web component is cool.

@robingamedev
Copy link

  • You are not exporting anything, so no export required. Its a single anonymous call.
  • Apple for over 10 years now has stated they will never implement is= syntax (extending existing elements)
  • An Empty constructor call (only calling super()) is pointless , as each callback will call its parent callback by default; So if you do not declare a connectedCallback, the connectedCallback on the parent is called; I think they call it OOP
  • this.textContext only workds becuase the component in this case is defined after DOM is parsed.
    Place it in the HEAD and not in a file that loads after DOMContentLoaded and your code will fail.
  • ::slotted() only works on DOM elements that get slotted, not on text elements
<copyright-year>Microsoft</copyright-year>
<copyright-year guri="guidelines URI">Apple</copyright-year>
<copyright-year fixed guri="guidelines URI">Meta</copyright-year>
<copyright-year fixed guri="guidelines URI"><b>Google</b></copyright-year>
<script>
  customElements.define("copyright-year", class extends HTMLElement {
    constructor() {
      const createElement = (tag, props = {}) => Object.assign(document.createElement(tag), props);
      const spacer = "&nbsp;".repeat(3);
      super()
        .attachShadow({
          mode: "open"
        })
      .append(
        createElement("style", {
          innerHTML: 
          `:host{display:block;margin:0;padding:0;width:100%;background:beige}`+
          `:host(*){font-size:120%}`+
          `::slotted(*){font-size:120%;color:red}`+
          `:host([fixed]){display:inline-block;position:fixed;bottom:0}`
        }),
        createElement("span", {
          innerHTML: "©️ " + (new Date().getFullYear()) + spacer
        }),
        createElement("slot", {
          innerHTML: `by ACME`
        }),
        createElement("span",{
        	innerHTML: this.getAttribute("guri") 
          	? spacer + `<A href="${this.getAttribute("guri")}">Read our guidelines</a>` 
            : ""
        })
      );
    }
  });

</script>

Putting it in the head script like that defeats the purpose of a web component.

The safari bit is valid.

@Danny-Engelman
Copy link

Putting it in the head script like that defeats the purpose of a web component.

Depends on the use case. if you need 10 SVG icons be done ASAP, without FOUCs, you declare one <svg-icon> Web Component the head of your document. Just like any GUI stuff

A copyright notice below the fold can be aysnc/lazy/module loaded

@urbanisierung
Copy link

<3

@KooiInc
Copy link

KooiInc commented Mar 30, 2024

Mmm, looks a lot like the code in my Stackblitz project (see createCopyrightComponent). It uses a webcomponent helper module that doesn't need the class syntax.

@liamnewmarch
Copy link

liamnewmarch commented Apr 3, 2024

Thanks for sharing this @ceving!

@Danny-Engelman, I think you’re trying to help but your code review comes across as critical. Reviews can be tricky but try to give helpful pointers and justifications rather than opinions.

You are not exporting anything, so no export required. Its a single anonymous call.

I see what you’re saying but there’s nothing wrong with exporting the class. It can be helpful to export components so they can be extended later on.

I would go for export class CopyrightYearElement ... for the sake of readability instead of hoisting the export to the top of the script, but this is fine.

Apple for over 10 years now has stated they will never implement is= syntax (extending existing elements)

Fair enough. Your recommendation here is to extend HTMLElement instead of HTMLSpanElement, remove the {extends: 'span'} option from the definition, and not to recommend using the is attribute.

An Empty constructor call (only calling super()) is pointless , as each callback will call its parent callback by default; So if you do not declare a connectedCallback, the connectedCallback on the parent is called; I think they call it OOP

Also fair enough. This is called inheritance – the constructor is inherited from the parent class, HTMLElement – and is a key part of object oriented programming or OOP.

this.textContext only works for you because the component in this case is defined after DOM is parsed.
Place it in the HEAD and not in a file that loads after DOMContentLoaded and your code will fail.

The code in the gist will not fail if you follow the instructions. You’re right that connectedCallback can be called before the component’s children are parsed, but the gist says to include the module with <script type="module"> which defers execution of the script until after the DOM is ready.

::slotted() only works on DOM elements that get slotted, not on text elements

I might be missing something but no revision of the gist seems to use ::slotted()…?


Hope that helps 🙂

I’d like to critique the code you shared too. One tip to make things more readable is to tag the code block with the langugage you’re using, this enables syntax highlighting. edit: My bad, I was looking at one of the replies.

<copyright-year>Microsoft</copyright-year>
<copyright-year guri="guidelines URI">Apple</copyright-year>
<copyright-year fixed guri="guidelines URI">Meta</copyright-year>
<copyright-year fixed guri="guidelines URI"><b>Google</b></copyright-year>

I’m not sure where the fixed and guri="..." attributes came from but they seem specific to your use-case and you may want to remove them.

<script>

As mentioned, <script> is fine but you can run into ordering bugs if the script isn’t the last thing in your <body>. You might want to consider using <script type="module"> or <script defer> which is the current best practice and means you can include it in your <head> with no problems.

  customElements.define("copyright-year", class extends HTMLElement {
    constructor() {
      const createElement = (tag, props = {}) => Object.assign(document.createElement(tag), props);

This is a neat utility function but it doesn’t feel like it should live in the constructor of this web component.

      const spacer = "&nbsp;".repeat(3);

This also feels like it might be specific to your use-case. If it’s possible you might want to consider using CSS instead of non-breaking spaces.

      super()
        .attachShadow({ mode: "open" })
        .append(
            createElement("style", {
              innerHTML: 
              `:host{display:block;margin:0;padding:0;width:100%;background:beige}`+
              `:host(*){font-size:120%}`+
              `::slotted(*){font-size:120%;color:red}`+
              `:host([fixed]){display:inline-block;position:fixed;bottom:0}`
            }),

I won’t review the CSS which seems custom, but it looks like a lot of this could be moved out of the shadow DOM which would save you from having to minify it manually. Bear in mind that code is read more times than it’s written, so if you’re able to write this out fully and minify it as part of a build step then future developers (or even yourself) will thank you.

            createElement("span", {
              innerHTML: "©️ " + (new Date().getFullYear()) + spacer
            }),

I would recommend using textContent over innerHTML as a best practice where you can to avoid potential XSS injection attacks, although this case should be fine. I noticed you used &nbsp; above and the unicode copywright character here, so it might be good to be consistent.

            createElement("slot", {
              innerHTML: `by ACME`
            }),
            createElement("span",{
        	    innerHTML: this.getAttribute("guri") 
          	    ? spacer + `<A href="${this.getAttribute("guri")}">Read our guidelines</a>` 
                : ""
            })

Again this content feels specific to your use-case. If you add the <a> tag as a child in the HTML where this is used it would help keep the component generic.

          );
        }
    });

</script>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment