【JointJS】カスタム要素にHTMLのタグを使用する

## はじめに

この記事はJointJSのカスタム要素で、SVG外の通常のHTMLタグを使用するための方法を紹介します。
この記事を読むためには[昨日の記事](https://qiita.com/acnaman/items/d9eb80ce25ebd50749d9)のカスタム要素の作り方の知識が前提となります。カスタム要素自体の作り方がわからない方は、まずそちらの記事をご確認ください。

https://qiita.com/acnaman/items/d9eb80ce25ebd50749d9

## HTMLはそのままでは利用できない

JointJSで描画される図形は、最終的にはSVGのタグで出力されます。
SVGの中にHTMLのタグをそのまま入れてしまうと、構文エラーとなり図形が描画されなくなってしまいます。

## 利用するには`foreignObject`を使用する

ではHTMLタグは利用できないのか、というとそうではありません。SVGには`<foreignObject>`という要素が用意されており、この要素の中ではHTMLのタグを使用できます。

### コード例

```js
joint.dia.Element.define('example.MyHtml', {
  size : { width: 300, height: 300 },
  attrs: {
    foreignObject: {
      width: 'calc(w)',
      height: 'calc(h)'
    }
  }
},
{
  markup: [{
    tagName: 'foreignObject',
    selector: 'foreignObject',
    children: [
      {
        tagName: 'div',
        namespaceURI: 'http://www.w3.org/1999/xhtml',
        selector: 'background',
        style: {
          backgroundColor: '#131e29',
          height: '100%'
        },
        children: [
          {
            tagName: 'p',
            selector: 'text',
            style: {
              color: '#F2F2F2',
              font: '16px sans-serif',
              padding: '10px',
              margin: 0,
            },
            textContent: 'ID'
          },
          {
            tagName: 'input',
            selector: 'input',
            style: {
              color: '#F2F2F2',
              font: '16px sans-serif',
              padding: '10px',
              margin: '10px',
            },
            textContent: 'ID'
          },
           {
            tagName: 'p',
            selector: 'textu',
            style: {
              color: '#F2F2F2',
              font: '16px sans-serif',
              padding: '10px',
              margin: 0,
            },
            textContent: 'パスワード'
          },
          {
            tagName: 'input',
            selector: 'inputu',
            style: {
              color: '#F2F2F2',
              font: '16px sans-serif',
              padding: '10px',
              margin: '10px',
            },
            textContent: 'ID'
          },        ]
      }
    ]
  }]
},
{
  // コンストラクタ
  create: function(x, y) {
    const html = new joint.shapes.example.MyHtml();
    html.position(x, y);
    
    return html;
  }
});

graph.addCells(html1, html2);
```

### 実行結果[^1]

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/129526/bd9d5ed3-1324-9f79-99b4-be61b3f50af4.png)


## `markup`にタグの記述をそのまま書く

今までのコード例では、`markup`にjson形式のデータを指定してきました。ですが、実際の開発では既存のHTMLをJointJSに移行するなどの状況で、HTMLをそのままテンプレートとして使用したいという場合もあります。その場合は`joint.util.svg`を使用することで、`markup`に元々のHTMLの形式でテンプレートを書くことができます。

### コード例

```js
{
  markup: joint.util.svg`
    <foreignObject @selector="foreignObject>">
      <div xmlns="http://www.w3.org/1999/xhtml"> 
        <p>ID</p>
        <input type="text"/>
        <p>パスワード</p>
        <input type="text" />
      </div>
    </foreignObject>
  `
},
```


## どういうときに`foreignObject`を使用するか？

既存のHTMLデザインがあり、それをJointJSへ流用したいときに、既存のHTMLの変更を最小限に抑えて流用できます。当たっていたCSSもそのまま適用できます。
`<input>`タグなども使用できますので、ユーザーの記入が必要なUIを提供する場合にも利用できます。

※この記事は [JointJS Advent Calendar 2023](https://qiita.com/advent-calendar/2023/jointjs) の記事です。他の記事を読む場合はカレンダーのページを参照してください。

https://qiita.com/advent-calendar/2023/jointjs

[^1]:実際はIDとパスワードを2つ入力させるようなUIは作らないと思いますが、inputタグを再利用するパターンとしてご理解ください。