<div> <div> <slot></slot> </div> <div> <div class="inline-div"> <slot name="inner1"></slot> </div> <div class="inline-div"> <slot name="inner2"></slot> </div> </div> </div>
とりあえずソースコードは次のようになります。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>WebContent Sample</title>
<script>
class TwoSlot extends HTMLElement {
static get template() {
return `
<div>
<div>
<slot></slot>
</div>
<div>
<div class="inline-div">
<slot name="inner1"></slot>
</div>
<div class="inline-div">
<slot name="inner2"></slot>
</div>
</div>
</div>
<style>
.inline-div {
display: inline-block;
}
</style>
`;
}
constructor(){
super();
// // こっちに書いても<slot>の変更は効く
// // 多分、JSでやっている更新処理が効かない
// let sr = this.attachShadow({mode: "open"});
// sr.innerHTML = TwoSlot.template;
}
connectedCallback(){
let sr = this.attachShadow({mode: "open"});
sr.innerHTML = TwoSlot.template;
}
}
customElements.define('two-slot', TwoSlot);
</script>
</head>
<body>
<two-slot id="TwoSlotSample">
<p slot="inner1" class="left">hoge</p>
<p slot="inner2" class="right">fuga</p>
<div class="main">
<h1>タイトル</h1>
<div>
これはメインのコンテンツです。
</div>
</div>
</two-slot>
<script>
let elm = document.getElementById("TwoSlotSample");
elm.querySelector(".left").innerHTML = `
<span style="color:red; border:solid red 1px;">FOO</span>
`;
</script>
</body>
</html>
このカスタムタグを作るためのクラスです。
<slot>は3個あるのにTwoSlotなのは色々試している間にスロットが1つ増えてしまったせいです。気にしないでください。
まず、このクラスはHTMLElementを継承しています。
カスタムタグはHTMLElementクラスを継承しなければならないので、こういうものだと思っておきます。
コンストラクタです。
実際にはsuper()でHTMLElementのコンストラクタを呼んでいるだけです。今回のサンプルはメンバ変数を持たないのでこれだけです。
コメントアウトしてありますが、次に説明するconnectedCallback()の処理をこちらに書いてもこのサンプルの内容だと同じ様に動作します。
これは、このカスタムタグが生成された後はHTMLの機能だけしか使わないのでconstructorに書いても同じことになるからですが、どこかのWebページにconnectedCallback()に書くべきとか書いてあったのでこちらをコメントアウトしてあります。
でもMDNによると、
constructor():要素の作成またはアップグレード時に呼び出されます
connectedCallback():要素がドキュメントに挿入されたときに呼び出される、シャドウツリーへの呼び出し
とあるので、constructor()でも良い様な気がします?
4つあるライフサイクルコールバックの一つで、先にも書いた通り、
「要素がドキュメントに挿入されたときに呼び出される、シャドウツリーへの呼び出し」
のコールバックメソッドです。
このTwoSlotタグがドキュメントに挿入された時に呼び出されてシャドウルート以下にtemplate()のHTMLを作るためにここで実装しています。
this.attachShadow()でシャドウルートをこのエレメント(TwoSlotタグ)にアタッチしてその参照を得ます。
それからシャドウルートのinnerHTMLにHTML文字列を突っ込むとシャドウルート以下に目的のHTMLが構成されます。
これでカスタムタグの出来上がりです。
50〜59行目で実際にタグを使っています。
実際の表示は次の様になります。
現在ではChrome以外では機能しませんが次の様になります(ページの表示上の問題でサンプルコード中の<h1>を<strong>に変えています)。
hoge
fuga
HTMLのソースでは「タイトル」と「これはメインのコンテンツです。」が「hoge」、「fuga」の下に書かれているのに、カスタムタグに適用されたことで「タイトル」と「これはメインのコンテンツです。」が上に、「hoge」と「fuga」は下に書かれ、「hoge」と「fuga」は横に並んで表示されるのでカスタムタグが機能していることがわかります。
下の表示はサンプルコードの最下部にある<script>内の処理を適用した場合です。「hoge」の部分を書き換えるとそれが反映されます。
hoge
fuga
これは<slot>タグの機能です。
<slot>タグはカスタムタグの子要素をカスタムタグの内部に反映するためのタグで、名前付きと名前なしのものがあります。
名前付きのスロットに対応する要素は「<p slot="inner1" class="left">hoge</p>」の様にslot属性で名前を指定します。
名前を指定しなかった子要素は全部名前なしの<slot>タグに反映される様です。
この<slot>は非常に便利です。
<slot>は対応するカスタムタグの子要素の参照になっています。
シャドウルート以下の要素は外部から隔離されていて触れないのですが、カスタムタグの子要素は触ることができます。
なので、カスタムタグの子要素を変更することでカスタム要素内のタグの構成やスタイルを変更できる上、特になんの処理を実装しなくてもその変更を即座に反映することができます。
そして単なる参照なので要素の追加削除などは行われず、反映に必要な負荷は再レンダリングだけで済むと思われます。