[macOS] スタンドアロンの FileMaker Pro と localhost との連携 vol.4

## 使用環境

- Claris FileMaker Pro 21.1.41 (macOS)
- macOS Sequoia 15.3.1

## ページ遷移を FileMaker Pro に反映させる必要性

 FileMaker Pro の Web ビューアは、Web ブラウザを FileMaker レイアウトに組み込むようなものですから、Web ページのリンクも動作します。
 しかし、FileMaker レイアウトの代替として、Web ページを使う場合、リンクで飛んでしまっては困ります。
 FileMaker から見て、同じコンテキスト（この場合、表示中のレイアウトのテーブルオカレンス）の同一レコードを別のデザインで表示するリンク以外は、FileMaker がコンテキストを見失うということになりますので、常に、どのコンテキストで、どのレコードが表示されているのか、FileMaker 側で把握できなければ困るわけです。

 ということは、基本的に、Web ソース側で単純な `<a href="...">` という記述はできないということになります。

## localhost 側

 ページ遷移を広い意味で捉えてると、様々なパターンがありますが、ここでは、単純な `<a href="...">` の代替方法を説明します。
 今回は、FileMaker Pro の localhost連携の開発スタイルを含めてやっていきますので、必ずしも、ページ遷移に必要ではないこともやっていますので、予めご了承ください。

 これも、前回と同様、JavaScript 関数と FileMaker スクリプトの連携動作になります。

 今回も単純なページを作ってみましょう。localhost のドキュメントルートに `fmsite2.com/` ディレクトリを作成し、そこで作業することにします。
　ドキュメントルートは、前回までと同様、`~/Sites/WebServer/Documents/` という設定です。

```.sh:ターミナル
mkdir ~/Sites/WebServer/Documents/fmsite2.com/
cd ~/Sites/WebServer/Documents/fmsite2.com/
mkdir css
mkdir js
mkdir dashboard
mkdir project
```

  このようなディレクトリ構造にして、最終的に、次のような構造にするとします。
```
.
├── css
│   └── fmsite2-style.css
├── dashboard
│   └── index.html
├── index.html
├── js
│   └── fmsite2-plugin.js
└── project
    └── index.html
```

　コンテンツごとにディレクトリを作成しています。
  `dashboard/`、`project/` というのがコンテンツに相当しますが、サンプルですので、内容があるわけじゃありません。ここは何でも構いません。

　このサイトのホームは、`dashboard/index.html` という設定です。`http://localhost/fmsite2.com/` でアクセスしてきたときに、そこに飛ばすため、`fmsite2.com/index.html` を置いています。
```.html:fmsite2.com/index.html
<!DOCTYPE html>
<html lang="ja">
	<head>
		<meta charset="UTF-8">
		<meta http-equiv="refresh" content="0; url=./dashboard/">
	</head>
</html>

```

 コンテンツの中身は例によって、[Bootstrap Examples](https://getbootstrap.com/docs/5.3/examples/) を利用して簡単に作っています。今回、利用しているのは、Offcanvas navbar、Sticky footer、Heroes の３つです。これで、残りのファイルを作っています。
```.html:fmsite2.com/dashboard/index.html
<!DOCTYPE html>
  <html lang="ja" class="h-100">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="description" content="{$description}">
    <meta name="author" content="{$author}">
    <title>Page 1</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
    <link rel="stylesheet" href="../css/fmsite2-style.css">
  <title>Page 1</title>
</head>

<body class="d-flex flex-column h-100"">

  <!-- offcanvas-navbar -->
  <nav class="navbar navbar-expand-lg fixed-top navbar-dark bg-dark" aria-label="Main navigation">
    <div class="container-fluid">
      <a class="navbar-brand" onclick="hyperReference('dashboard/');">ブランド名</a>
      <button class="navbar-toggler p-0 border-0" type="button" id="navbarSideCollapse" aria-label="Toggle navigation">
        <span class="navbar-toggler-icon"></span>
      </button>

      <div class="navbar-collapse offcanvas-collapse" id="navbarsExampleDefault">
        <ul class="navbar-nav me-auto mb-2 mb-lg-0">
          <li class="nav-item">
            <a class="nav-link active" aria-current="page" onclick="hyperReference('dashboard/');">ダッシュボード</a>
          </li>

          <li class="nav-item">
            <a class="nav-link" href="#">顧客管理</a>
          </li>

          <li class="nav-item">
            <a class="nav-link" onclick="hyperReference('project/');">プロジェクト</a>
          </li>

          <li class="nav-item">
            <a class="nav-link" href="#">チーム</a>
          </li>

          <li class="nav-item dropdown">
            <a class="nav-link dropdown-toggle" href="#" data-bs-toggle="dropdown" aria-expanded="false">アカウント</a>
            <ul class="dropdown-menu">
              <li><a class="dropdown-item" href="#">メンバー</a></li>
              <li><a class="dropdown-item" href="#">フォロー</a></li>
              <li><a class="dropdown-item" href="#">設定</a></li>
            </ul>
          </li>
        </ul>
        <form class="d-flex" role="search">
          <input class="form-control me-2" type="search" placeholder="クイック検索" aria-label="Search">
          <button class="btn btn-outline-success" style="width: 6rem;" type="submit">検索</button>
        </form>
      </div>
    </div>
  </nav>

  <!-- hero -->
  <main class="flex-shrink-0">

    <div class="px-4 py-5 my-5 text-center">
      <img class="d-block mx-auto mb-4" src="https://getbootstrap.com/docs/5.3/assets/brand/bootstrap-logo.svg" alt="" width="72" height="57">
      <h1 class="display-5 fw-bold text-body-emphasis">ダッシュボード</h1>
      <div class="col-lg-6 mx-auto">
        <p class="lead mb-4">スタンドアロンで動作中の FileMaker Pro と localhost との連携</p>
        <div class="d-grid gap-2 d-sm-flex justify-content-sm-center">
          <button type="button" class="btn btn-primary btn-lg px-4 gap-3">顧客管理</button>
          <button type="button" class="btn btn-outline-secondary btn-lg px-4">チーム</button>
        </div>
      </div>
    </div>

  </main>

  <!-- sticky-footer -->
  <footer class="footer mt-auto py-3 bg-body-tertiary">
    <div class="container">
      <span class="text-body-secondary">Copyright &copy; 2025 会社名. All rights reserved.</span>
    </div>
  </footer>

  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
  <script src="../js/fmsite2-plugin.js"></script>
</body>
</html>

```

```.html:fmsite2.com/project/index.html
<!DOCTYPE html>
  <html lang="ja" class="h-100">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="description" content="{$description}">
    <meta name="author" content="{$author}">
    <title>Page 1</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
    <link rel="stylesheet" href="../css/fmsite2-style.css">
  <title>Page 1</title>
</head>

<body class="d-flex flex-column h-100"">

  <!-- offcanvas-navbar -->
  <nav class="navbar navbar-expand-lg fixed-top navbar-dark bg-dark" aria-label="Main navigation">
    <div class="container-fluid">
      <a class="navbar-brand" onclick="hyperReference('dashboard/');">ブランド名</a>
      <button class="navbar-toggler p-0 border-0" type="button" id="navbarSideCollapse" aria-label="Toggle navigation">
        <span class="navbar-toggler-icon"></span>
      </button>

      <div class="navbar-collapse offcanvas-collapse" id="navbarsExampleDefault">
        <ul class="navbar-nav me-auto mb-2 mb-lg-0">
          <li class="nav-item">
            <a class="nav-link" onclick="hyperReference('dashboard/');">ダッシュボード</a>
          </li>

          <li class="nav-item">
            <a class="nav-link" href="#">顧客管理</a>
          </li>

          <li class="nav-item">
            <a class="nav-link active" aria-current="page" onclick="hyperReference('project/');">プロジェクト</a>
          </li>

          <li class="nav-item">
            <a class="nav-link" href="#">チーム</a>
          </li>

          <li class="nav-item dropdown">
            <a class="nav-link dropdown-toggle" href="#" data-bs-toggle="dropdown" aria-expanded="false">アカウント</a>
            <ul class="dropdown-menu">
              <li><a class="dropdown-item" href="#">メンバー</a></li>
              <li><a class="dropdown-item" href="#">フォロー</a></li>
              <li><a class="dropdown-item" href="#">設定</a></li>
            </ul>
          </li>
        </ul>
        <form class="d-flex" role="search">
          <input class="form-control me-2" type="search" placeholder="クイック検索" aria-label="Search">
          <button class="btn btn-outline-success" style="width: 6rem;" type="submit">検索</button>
        </form>
      </div>
    </div>
  </nav>

  <!-- hero -->
  <main class="flex-shrink-0">

    <div class="px-4 py-5 my-5 text-center">
      <img class="d-block mx-auto mb-4" src="https://getbootstrap.com/docs/5.3/assets/brand/bootstrap-logo.svg" alt="" width="72" height="57">
      <h1 class="display-5 fw-bold text-body-emphasis">プロジェクト</h1>
      <div class="col-lg-6 mx-auto">
        <p class="lead mb-4">スタンドアロンで動作中の FileMaker Pro と localhost との連携</p>
        <div class="d-grid gap-2 d-sm-flex justify-content-sm-center">
          <button type="button" class="btn btn-primary btn-lg px-4 gap-3">顧客管理</button>
          <button type="button" class="btn btn-outline-secondary btn-lg px-4">チーム</button>
        </div>
      </div>
    </div>

  </main>

  <!-- sticky-footer -->
  <footer class="footer mt-auto py-3 bg-body-tertiary">
    <div class="container">
      <span class="text-body-secondary">Copyright &copy; 2025 会社名. All rights reserved.</span>
    </div>
  </footer>

  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
  <script src="../js/fmsite2-plugin.js"></script>
</body>
</html>
```

```.css/fmsite2.com/css/fmsite2-style.css
html,
body {
  overflow-x: hidden; /* Prevent scroll on narrow devices */
}

body {
  padding-top: 56px;
}

@media (max-width: 991.98px) {
  .offcanvas-collapse {
    position: fixed;
    top: 56px; /* Height of navbar */
    bottom: 0;
    left: 100%;
    width: 100%;
    padding-right: 1rem;
    padding-left: 1rem;
    overflow-y: auto;
    visibility: hidden;
    background-color: #343a40;
    transition: transform .3s ease-in-out, visibility .3s ease-in-out;
  }
  .offcanvas-collapse.open {
    visibility: visible;
    transform: translateX(-100%);
  }
}

.nav-scroller .nav {
  color: rgba(255, 255, 255, .75);
}

.nav-scroller .nav-link {
  padding-top: .75rem;
  padding-bottom: .75rem;
  font-size: .875rem;
  color: #6c757d;
}

.nav-scroller .nav-link:hover {
  color: #007bff;
}

.nav-scroller .active {
  font-weight: 500;
  color: #343a40;
}

.bg-purple {
  background-color: #6f42c1;
}

```

```.js:fmsite2.com/js/fmsite2-plugins.js
(() => {
  'use strict'

  document.querySelector('#navbarSideCollapse').addEventListener('click', () => {
    document.querySelector('.offcanvas-collapse').classList.toggle('open')
  })
})()

function hyperReference (hyperReference)
{
  const parameters	= {
      hyperReference
  };

  const parameterJSON	= JSON.stringify(parameters);

  FileMaker.PerformScriptWithOption('HyperReference', parameterJSON, '0');
}
```

　これで、localhost 側は完成です。以下のようなページが表示されます。
 ![スクリーンショット 2025-03-07 11.42.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3833520/519a0a57-80f9-467f-983e-d79f03779b9d.png)

　このページで、今回、有効にするのは、「ブランド名」、「ダッシュボード」、「プロジェクト」の３ヶ所です。
　この３ヶ所のリンクは、`<a href="">` ではなく、`<a onclick="">` を使っています。

```.html:該当箇所
<a class="navbar-brand" onclick="hyperReference('dashboard/');">ブランド名</a> 

<a class="nav-link active" aria-current="page" onclick="hyperReference('dashboard/');">ダッシュボード</a>

<a class="nav-link" onclick="hyperReference('project/');">プロジェクト</a>
```

　`onclick` で、リンク先のディレクトリ名を引数にして、`hyperReference()` という関数を呼んでいます。

　`hyperReference()` 関数は `js/fmsite2-plugin.js` の中にあります。
```.js:hyperReference()
function hyperReference (hyperReference)
{
  const parameters	= {
      hyperReference
  };

  const parameterJSON	= JSON.stringify(parameters);

  FileMaker.PerformScriptWithOption('HyperReference', parameterJSON, '0');
}

```

 `onclick` で渡された引数を、`hyperReference` をキーにして JSON 化。これを、`FileMaker.PerformScriptWithOption()` の第２引数にしています。第１引数は、FileMaker Pro のスクリプト名で、`HyperReference` です。

## FileMaker Pro 側

　新しい FileMaker ファイル `fmsite2.fmp12` を作って、２つの新しいテーブル、`dashboard` と`project` を作成します。中身は今回は手をつけません。

　`データベースの管理 > リレーションシップ` で機能ごとにテーブルオカレンスを色付けしておきます。
![スクリーンショット 2025-03-07 11.55.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3833520/71f2a997-97b4-433f-9fd3-e5490e3f7bc8.png)

 こうして置くと、あとで判別しやすくなります。この２つのテーブルオカレンスを複製し、今回使うテーブルオカレンスを作成します。
 これはプリンタ設定をA3横に設定して、プリンタのページ境界を表示しておいて、機能ごとにページを分けて作ると管理しやすくなります。
![スクリーンショット 2025-03-07 11.58.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3833520/c96e7404-b04d-4361-9185-96419ccc3826.png)

　複製したテーブルオカレンスは、それぞれ`_DASHBOARD`、`_PROJECT`と命名しました。

 自動で出来ているレイアウト、`dashboard` と `project` のテーブルオカレンスを複製した方に変更します。また、名称は、localhost のディレクトリ名 `dashboard/`、`project/` とします。
![スクリーンショット 2025-03-07 12.01.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3833520/48660ada-9f9a-4e88-b334-fc4dd8b9f130.png)

 レイアウトモードで、Web ビューアを配置します。
 ![スクリーンショット 2025-03-07 12.02.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3833520/0e58017a-6606-40ef-a7df-620b4b8ed735.png)

レイアウトパート: ボディのみ
ボディサイズ: 390 x 844
Web ビューア位置: 0, 0
Web ビューアサイズ: 390 x 844
自動サイズ調整: 上下左右をロック
Web ビューア名前: それぞれ、dashboard-viewer, project-viewer
Web ビューア塗りつぶし: なし
Web ビューア線: なし
```.calc:Web ビューア カスタム Web アドレス
"http://localhost/fmsite2.com/" &
Get ( レイアウト名 )
```

　片方作ったら、Web ビューアをコピー＆ペーストして、名前を変更するだけです。

 次に、スクリプトです。
```.vb:HyperReference
変数を設定 [ $json ; 値: Get ( スクリプト引数 ) ]
# 
レイアウト切り替え [ JSONGetElement ( $json ; "hyperReference" ) ; アニメーション: なし ]

```

 `FileMaker.PerformScriptWithOption()` から渡される JSON のキー `hyperReference` の値を取り出して、それを使ってレイアウト切り替えを行なっています。

```math
localhost のディレクトリ名/ファイル名 = FileMaker Pro のレイアウト名
```

 こういう規約でやっているので、何も考える必要ないですね。同様にテーブル名やフィールド名もなるべく、JSON キーと一致するようにしておけば、本旨でない部分に頭を使う必要がなくなります。

  今回、渡すのは一つの文字列だけですので、JSON 化することが必須ではありませんが、JSON で渡すという規約にしておくと、複数の値が必要になったとき対応しやすいです。
  ページ遷移でいえば、`GET` でパラメータを渡す場合など、値が複数になるので、JSON 渡しが前提になっていれば、すぐに応用できるでしょう。

 以上で、もうページ遷移ができるようになっています。
 ![スクリーンショット 2025-03-07 12.17.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3833520/da9fb4a8-5181-447f-8827-0d0a0ef14dfd.png)

 レイアウト名に着目すると`dashboard/` になっていますね。
 この状態で、プロジェクトをクリックすると、

![スクリーンショット 2025-03-07 12.17.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3833520/c2e8873b-02fd-4252-819e-fe2895562238.png)

 localhost のページが切り替わり、FileMaker Pro のレイアウトも切り替わって `project/` になっています。

 これで、今回の目的は達成できました。