FileMaker Data API を使う 2025年版 vol.12

 サンプルソリューションのデータは、[新しいサンプルデータを作る](https://qiita.com/RemixWorksLLC/items/04a5e616e59eb062bba9#新しいサンプルテーブルを作る) を参照してください。

  この記事で使用するソースコードは、[PHP 化の叩き台にする HTML ソースコード](https://qiita.com/RemixWorksLLC/items/df47f271da6f7693d0a4#php-化の叩き台にする-html-ソースコード) 以下にある次のソースコードです。
 - edit-record.html
 - bootstrap-common.css
 - fmda-form.css
 - fmda-form-check.js

 以上を、[前回記事](https://qiita.com/RemixWorksLLC/items/1a1956d659690519638a)で、変更しており、以下の構成になっています。

```
.
├── css
│   ├── bootstrap-common.css
│   └── fmda-form.css
├── html
│   └── edit-record.html
├── index.php
├── js
│   └── fmda-form-check.js
├── json
│   └── html-contents.json
└── php
    ├── bs-frame-class.php
    ├── php-functions.php
    └── preferences.php

```

## ページの識別

　現在のプランでは、共通の骨組みを使って、FileMaker Data API の作成・編集・削除・複製のリクエストを実行するページを作るという方針になっています。
 しかし、ほとんど同じなのに、作成・編集・削除・複製で４つもページを作るのは、なんだか面倒な気がします。
 それよりもシングルページで４つの機能を持っている方が、現在は、PHP ですが、後々、JavaScript で非同期スタイルに改めたときを考えると良さそうです。

 しかし、４つの機能は、前後の処理や、FileMaker Data API のリクエストが異なりますので、なんらかの方法で判別させなければいけません。
 大昔の CGI プログラムなら、GET で、`?page=1` のような URL で解決したと思います。
 同じような考え方で、GET ではなく、POST でやる想定をしておきましょう。

 最初のアクセス、またはフッタ部分のリンクのクリックで、機能を選べるようにページの識別子を仕込んでおくことにします。

 <table>
  <caption>ページの識別</caption>
  <thead>
    <tr>
      <th>ページの識別子</th>
      <th>機能</th>
      <th>FileMaker Data API リクエスト</th>
    </tr>
  </thead>
  <tr>
    <td> 0 </td>
    <td>新規作成</td>
    <td>records - Create Record</td>
  </tr>
  <tr>
    <td> 1 </td>
    <td>編集</td>
    <td>records - Edit Record</td>
  </tr>
  <tr>
    <td> 2 </td>
    <td>削除</td>
    <td>records - Delete Record</td>
  </tr>
  <tr>
    <td> 3 </td>
    <td>複製</td>
    <td>records - Duplicate Record</td>
  </tr>
</table>

　以上のように規定し、デフォルトを 1 とします。
 これを`name="page-identity"` で POST で送信し、`$_POST['page-identity']` を調べます。
 これがエラーになった場合は、デフォルトの 1 を選択したと解することにして、`index.php` の冒頭に、この処理を `preferences.php` を読み込む前に追加します。

### index.php 冒頭部分
```
<?php

require_once(__DIR__ . '/php/bs-frame-class.php');

require_once(__DIR__ . '/php/php-functions.php');

@$page_indentity    = $_POST['page-identity'];
$page_indentity     = $page_indentity === null ? 1 : $page_indentity;

require_once(__DIR__ . '/php/preferences.php');

```

　これで、識別子を取得できれば、ページの機能の特定はできそうです。

## 残りの HTML ソースコードの PHP 化

　残りは、固有のコンテンツ部分になります。
 手法としては、前回と同様、
 - preferences.php に設定項目を持たせて
 - その実際の内容は、html-contents.json に書き、
 将来の FileMaker Pro からの更新に対応させる
 - HTML の出力部分は、クラスのスタティックメソッドとする

　このような方針で、前述のページ識別を含め、一気に以下の条件で PHP 化してしまいます。

 - FileMaker Data API のリクエストのコードはまだ書かない
 - FileMaker ソリューションから得るべきデータは仮
 - フッタ部分のリンクは、ページ識別に対応させる
 - それ用に、JavaScript のコードを追加する
 - コンテンツ固有の HTML ソースコードは、別のクラスファイルにしておく

### index.php
```
<?php

require_once(__DIR__ . '/php/bs-frame-class.php');
require_once(__DIR__ . '/php/bs-builder-class.php');
require_once(__DIR__ . '/php/php-functions.php');
require_once(__DIR__ . '/php/filemaker-data-api-class.php');

@$page_indentity    = $_POST['page-identity'];
$page_indentity     = $page_indentity === null ? 1 : $page_indentity;

require_once(__DIR__ . '/php/preferences.php');

BsFrame::HtmlBegin($color_modes_js, $description, $author, $title, $bootstrap_css, $bs_css_integrity, $bs_frame_css, $custom_css);
BsFrame::LoadColorModesSvg();
BsFrame::ColorModes();

BsBuilder::MainBegin($logo_svg, $logo_svg_width, $page_heading[$page_indentity], $page_lead[$page_indentity]);

BsBuilder::InfoItem($info_heading, $info_number_of_items, $info_item_heading, $info_item_content);
BsBuilder::GetInfo();

BsBuilder::CreateForm($form_preferences, $form_controls);
BsBuilder::CreatePagination($page_indentity);
BsBuilder::MainEnd();
BsBuilder::CreateFooter($copyright_year, $copyright_holder, $link_items);

BsFrame::HtmlEnd($bootstrap_js, $bs_js_integrity, $custom_js);

```

### [bootstrap-common.css](https://qiita.com/RemixWorksLLC/items/df47f271da6f7693d0a4#bootstrap-commoncss)

### [fmda-form.css](https://qiita.com/RemixWorksLLC/items/df47f271da6f7693d0a4#fmda-formcss)

### bs-builder-class.php
```
<?php

class BsBuilder {

    static function MainBegin (
      string $logo_svg,
      string $logo_svg_width,
      string $heading,
      string $lead
    ): void
    {
      echo <<<_HTML_
  <div class="container">
    <main>
      <div class="py-5 text-center">
        <img class="d-block mx-auto mb-4" src="{$logo_svg}" alt="" width="{$logo_svg_width}">
        <h2>{$heading}</h2>
        <p class="lead">{$lead}</p>
      </div> <!-- / .py-5 .text-center -->
  
      <div class="row g-5">
  
  _HTML_;
  
    }
  
    static function InfoItem (
      string $info_heading,
      int $info_number_of_items,
      array &$info_item_heading,
      array &$info_item_content
    ): void
    {
      echo <<<_HTML_
  <div class="col-md-5 col-lg-4 order-md-last">
    <h4 class="d-flex justify-content-between align-items-center mb-3">
      <span class="text-primary">{$info_heading}</span>
      <span class="badge bg-primary rounded-pill">{$info_number_of_items}</span>
    </h4>
  
    <ul class="list-group mb-3">
  
  _HTML_; // begin
  
      for ($i = 0 ; $i < count($info_item_heading) ; $i++) {
  
        echo <<<_HTML_
  <li class="list-group-item d-flex justify-content-between lh-sm">
    <div>
      <h6 class="my-0">{$info_item_heading[$i]}</h6>
  
  _HTML_;
  
        if (is_numeric($info_item_content[$i])) {
          echo <<<_HTML_
    </div>
    <span class="text-success">$info_item_content[$i]</span>
  </li>
  
  _HTML_;
  
        } else {
          echo <<<_HTML_
      <small class="text-body-secondary">standings</small>
    </div>
  </li>
  
  _HTML_;
  
        } // if (is_numeric($info_item_content[$i]))
      } // for ($i = 0 ; $i < count($info_item_heading) ; $i++)
  
      echo "</ul>\n"; // end
    }
  
    static function GetInfo ( ): void {
      echo <<<_HTML_
    <form action="{$_SERVER['PHP_SELF']}" method="POST" class="card p-2">
      <div class="input-group">
        <input type="text" class="form-control" name="recordId" placeholder="recordId">
        <input type="hidden" class="visually-hidden" name="getInfo" value="true">
        <button type="submit" class="btn btn-secondary">取得する</button>
      </div>
    </form>
  </div>
  
  _HTML_;
  
    }
  
    static function CreateForm (
      array &$form_prefernces,
      array &$form_controls
    ): void {
  
      echo <<<_HTML_
  <div class="col-md-7 col-lg-8">
    <h4 class="mb-3">{$form_prefernces[0]}</h4>
      <form action="{$form_prefernces[1]}" method="POST" class="needs-validation" novalidate>
        <div class="row g-3">
      
  _HTML_; // begin
  
      foreach ($form_controls as $key => $value) {
        $required         = '';
        $secondary_label  = '';
        if ($value['required'] === '1')
          $required         = 'required';
        else
          $secondary_label  = '<span class="text-body-secondary">（任意）</span>';
  
        if ($value['type'] === 'select') {
          echo <<<_HTML_
  <div class="col-sm-6">
    <label for="{$value['id']}" class="form-label">{$value['label']}{$secondary_label}</label>
    <select class="form-select" id="{$value['id']}" name="{$value['name']}" {$required} {$value['disabled']}>
      <option value="">選択...</option>
  
  _HTML_;
  
          foreach ($value['option'] as $option_key => $option_value) {
            echo "<option>{$option_value['value']}</option>\n";
          }
          echo "</select>\n";
  
        } else {
          echo <<<_HTML_
  <div class="col-sm-6">
    <label for="{$value['id']}" class="form-label">{$value['label']}{$secondary_label}</label>
    <input type="{$value['type']}" class="form-control" id="{$value['id']}" name="{$value['name']}" placeholder="{$value['placeholder']}" {$required} {$value['disabled']}>
  _HTML_;
  
        } // if ($value['type'] === 'select')
  
        if (!empty($required)) {
          echo <<<_HTML_
  <div class="invalid-feedback">
    {$value['feedback']}
  </div>
  
  _HTML_;
  
        } // if (!empty($required))
  
        echo "</div>\n";
      } // foreach ($form_controls as $key => $value)
  
    echo <<<_HTML_
    </div> <!-- / .row -->
  
    <hr class="my-4">
  
    <button class="w-100 btn {$form_prefernces[2]} btn-lg" type="submit">{$form_prefernces[3]}</button>
  </form> <!-- / .needs-validation -->
  
  _HTML_;
  
    }
  
    static function CreatePagination (
      int $page_identity
    ): void
    {

      if ($page_identity !== 0) {
        echo <<<_HTML_
  <hr class="my-4">
  
  <nav class="container text-center my-4" aria-label="Search results pages">
    <div class="row justify-content-md-center">
      <div class="col-auto text-center">
        <ul class="pagination">
          <li class="page-item disabled">
            <a class="page-link">前</a>
          </li>
  
          <li class="page-item active" aria-current="page">
            <a class="page-link" href="#">1</a>
          </li>
  
          <li class="page-item"><a class="page-link" href="#">2</a></li>
          <li class="page-item"><a class="page-link" href="#">3</a></li>
          <li class="page-item"><a class="page-link" href="#">4</a></li>
          <li class="page-item"><a class="page-link" href="#">5</a></li>
          <li class="page-item"><a class="page-link" href="#">6</a></li>
          <li class="page-item"><a class="page-link" href="#">7</a></li>
          <li class="page-item"><a class="page-link" href="#">8</a></li>
          <li class="page-item"><a class="page-link" href="#">9</a></li>
          <li class="page-item"><a class="page-link" href="#">10</a></li>
  
          <li class="page-item">
            <a class="page-link" href="#">次</a>
          </li>
        </ul>
      </div>
    </div> <!-- / .row -->
  </nav>
  
  _HTML_;

      } // if ($page_identity !== 0)
  
    }
  
    static function CreateFooter (
      string $copyright_year,
      string $copyright_holder,
      array &$link_items
    ): void
    {
      echo <<<_HTML_
      <footer class="my-5 pt-5 text-body-secondary text-center text-small">
        <p class="mb-1">Copyright &copy; {$copyright_year} {$copyright_holder}. All rights reserved.</p>
        <ul class="list-inline">
  
  _HTML_;

      foreach ($link_items as $key => $value) {
        echo <<<_HTML_
<li class="list-inline-item"> 
  <form id="{$value['formId']}" method="POST">
    <input type="hidden" name="page-identity" value="{$value['pageIdentity']}">
  </form>
  <a href="#" id="{$value['anchorId']}">{$value['textContent']}</a>
</li>

_HTML_;

      }

      echo <<<_HTML_
    </ul>
  </footer>
</div> <!-- / .container -->
  
_HTML_;
  
    }
}

```

### [bs-frame-class.php](https://qiita.com/RemixWorksLLC/items/1a1956d659690519638a#phpbs-frame-classphp-2)

### filemaker-data-api-class.php
```
<?php

class FileMakerDataApi
{
    public $host, $version, $database, $username, $password, $layout;
    public $bearer_session_token;

    function __construct (string $host, string $version)
    {
        $this->host     = $host;
        $this->version  = $version;
    }

    /**
     * リクエスト
     **/

    // ログイン
    public function login (string $database, string $username, string $password): string
    {
		$this->database	= $database;
		$this->username = $username;
		$this->password = $password;

		$auth_value         = $username . ':' . $password;
		$authorization      = 'Authorization: Basic ' . base64_encode($auth_value);
		$content_type       = 'Content-Type: application/json';
		$curlopt_httpheader = array($authorization, $content_type);

		$endpoint        	= "https://{$this->host}/fmi/data/{$this->version}/databases/{$database}/sessions";
		
		return self::executeCurl($endpoint, 'POST', $curlopt_httpheader);
	}

    // ログアウト
    public function logOut(string $database, string $bearer_session_token): string
    {
		$endpoint    = "https://{$this->host}/fmi/data/{$this->version}/databases/{$database}/sessions/{$bearer_session_token}";

		return self::executeCurl($endpoint, 'DELETE');
	}

    // データベースセッションの検証
	public function validateSession (string $session_token): string
    {
		$authorization      = 'Authorization: Bearer ' . $session_token;
		$curlopt_httpheader = array($authorization);
		$endpoint        	= "https://{$this->host}/fmi/data/{$this->version}/validateSession";

		return self::executeCurl($endpoint, 'GET', $curlopt_httpheader);
	}

	// レコード範囲の取得
	public function getRecords(
		string $database,
		string $layout,
		string $bearer_session_token,
		array &$script_name_array,
		array &$script_param_array,
		int $_offset = 1,
		int $_limit = 100,
		string $_sort = ''
		): string
    {
		$this->layout		= $layout;

		$authorization      = 'Authorization: Bearer ' . $bearer_session_token;
		$curlopt_httpheader = array($authorization);

		// ソートの設定
		$_sort_string		= !empty($_sort) ? '&_sort=' . urlencode($_sort) : '';

        $script_prerequest			= '';
    	$script_prerequest_param	= '';
    	$script_presort				= '';
    	$script_presort_param		= '';
    	$script						= '';
    	$script_param				= '';
		// リクエスト処理前のスクリプト
		if ($script_name_array[0] !== '') {
			$script_prerequest	= '&script.prerequest=' . $script_name_array[0];
			$script_prerequest_param = '&script.prerequest.param=' . urlencode(json_encode($script_param_array[0]));
		}
		
		// ソート前のスクリプト
		if ($script_name_array[1] !== '') {
			$script_presort	= '&script.presort=' . $script_name_array[1];
			$script_presort_param = '&script.presort.param=' . urlencode(json_encode($script_param_array[1]));
		}

		// リクエスト処理後のスクリプト
		if ($script_name_array[2] !== '') {
			$script	= '&script=' . $script_name_array[2];
			$script_param = '&script.param=' . urlencode(json_encode($script_param_array[2]));
		}
		
		$endpoint        	= "https://{$this->host}/fmi/data/{$this->version}/databases/{$database}/layouts/{$layout}/records?_offset={$_offset}&_limit={$_limit}{$_sort_string}{$script_prerequest}{$script_prerequest_param}{$script_presort}{$script_presort_param}{$script}{$script_param}";
		return self::executeCurl($endpoint, 'GET', $curlopt_httpheader);
	}

	// 検索の実行
	public function findRecords(
		string $database,
		string $layout,
		string $bearer_session_token,
		string $request_body
		): string
	{
		$authorization      = 'Authorization: Bearer ' . $bearer_session_token;
		$content_type       = 'Content-Type: application/json';
		$curlopt_httpheader = array($authorization, $content_type);
		$endpoint        	= "https://{$this->host}/fmi/data/{$this->version}/databases/{$database}/layouts/{$layout}/_find";

		return self::executeCurl($endpoint, 'POST', $curlopt_httpheader, $request_body);
	}

	// スクリプトの実行
	public function executeScript(
		string $database,
		string $layout,
		string $bearer_session_token,
		string $script_name,
		string $script_parameters = ''
		): string
	{
		$authorization      = 'Authorization: Bearer ' . $bearer_session_token;
		$curlopt_httpheader = array($authorization);
		$script_parameters	= !empty($script_parameters) ? '?script.param=' . json_encode(json_decode($script_parameters)) : '';
		$endpoint        	= "https://{$this->host}/fmi/data/{$this->version}/databases/{$database}/layouts/{$layout}/script/{$script_name}{$script_parameters}";
		
		return self::executeCurl($endpoint, 'GET', $curlopt_httpheader);
	}

    /**
     * ユーティリティ
     **/

    // レスポンスコードの取得
	public function getMessagesCode (string $response): string
    {
		$response_array = self::json2array($response);
		return $response_array['messages'][0]['code'];
	}

    // Bearer Session Token 取得
    public function getBearerSessionToken (string $response): string
    {
		$response_array = self::json2array($response);
		$this->bearer_session_token = $response_array['response']['token'];

		return $this->bearer_session_token;
	}

    // JSON を 配列へ変換
	public function json2array (string $json): array
    {
		$json   = mb_convert_encoding($json, 'UTF8', 'ASCII,JIS,UTF-8,EUC-JP,SJIS-WIN');
		return json_decode($json, true);
	}

    // cURL 実行
    static function executeCurl(string $curlopt_url, string $curlopt_customrequest, array $curlopt_httpheader = [], string $current_postfields = ''): string
    {
		$current_postfields	= !empty($current_postfields) ? json_encode(json_decode($current_postfields)) : '{}';

		$curl   = curl_init();
	
		curl_setopt_array($curl, array(
			CURLOPT_URL => $curlopt_url,
			CURLOPT_RETURNTRANSFER => true,
			CURLOPT_ENCODING => '',
			CURLOPT_MAXREDIRS => 10,
			CURLOPT_TIMEOUT => 0,
			CURLOPT_FOLLOWLOCATION => true,
			CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
			CURLOPT_CUSTOMREQUEST => $curlopt_customrequest,
			CURLOPT_POSTFIELDS => $current_postfields,
			CURLOPT_HTTPHEADER => $curlopt_httpheader,
		));
	
		$response   = curl_exec($curl);
	
		curl_close($curl);
		return $response;
	}
}
```

### [php-functions.php](https://qiita.com/RemixWorksLLC/items/1a1956d659690519638a#phpphp-functionsphp)

### preferences.php
```
<?php

$html_contents_json_file  = __DIR__ . '/../json/html-contents.json';
$temp_array               = json_get_element($html_contents_json_file);

// for HtmlBegin(), LoadColorModesSvg(), ColorModes()
$color_modes_js   = $temp_array['contents']['colorModesJs'];
$description      = $temp_array['contents']['description'];
$author           = $temp_array['contents']['author'];
$title            = $temp_array['contents']['title'];
$bootstrap_css    = $temp_array['contents']['bootstrapCss'];
$bs_css_integrity = $temp_array['contents']['bsCssIntegrity'];
$bs_frame_css     = $temp_array['contents']['bsFrameCss'];
$custom_css       = [];
foreach ($temp_array['contents']['customCss'] as $key => $value) {
  array_push($custom_css, $value['location']);
}

// for MainBegin()
$logo_svg       = $temp_array['contents']['logoSvg'];
$logo_svg_width = $temp_array['contents']['logoSvgWidth'];
$page_heading   = [];
$page_lead      = [];
foreach ($temp_array['pageContents'] as $key => $value) {
    array_push($page_heading, $value['heading']);
    array_push($page_lead, $value['lead']);
}

$form_heading       = [];
$form_action        = [];
$form_button_color  = [];
$form_button_text   = [];
foreach ($temp_array['formContents'] as $key => $value) {
    array_push($form_heading, $value['heading']);
    if ($value['action'] === 'PHP_SELF')
        $value['action'] = "\$_SERVER['PHP_SELF']";
    array_push($form_action, $value['action']);
    array_push($form_button_color, $value['buttonColor']);
    array_push($form_button_text, $value['buttonText']);
}

// for InfoItem(), GetInfo()
$info_heading         = $temp_array['contents']['infoHeading'];
$info_number_of_items = $temp_array['contents']['infoNumberOfItems'];

$info_item_heading  = [];
$info_item_content  = [];
foreach ($temp_array['infoItems'] as $key => $value) {
  array_push($info_item_heading, $value['heading']);
  array_push($info_item_content, $value['content']);
}

// for CreateForm(), CreatePagination(), MainEnd()
$form_controls           = $temp_array['formControls'];

for ($i = 0; $i < count($form_controls); $i++) {
  $form_controls[$i]['disabled']  = $page_indentity >= 2 ? 'disabled' : '';
}

$form_preferences = [
  $form_heading[$page_indentity],
  $form_action[$page_indentity],
  $form_button_color[$page_indentity],
  $form_button_text[$page_indentity]
];

// for CreateFooter()
$copyright_year   = $temp_array['contents']['copyrightYear'];
$copyright_holder = $temp_array['contents']['copyrightHolder'];

$link_items       = $temp_array['linkItems'];

// for HtmlEnd()
$bootstrap_js     = $temp_array['contents']['bootstrapJs'];
$bs_js_integrity  = $temp_array['contents']['bsJsIntegrity'];
$custom_js        = [];
foreach ($temp_array['contents']['customJs'] as $key => $value) {
  array_push($custom_js, $value['location']);
}

```

### html-contents.json
```
{
    "contents" : {
        "colorModesJs" : "https://getbootstrap.com/docs/5.3/assets/js/color-modes.js",
        "description" : "FileMaker Data API テスト 及び サンプル",
        "author" : "",
        "title" : "records - Edit Record",
        "bootstrapCss" : "https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css",
        "bsCssIntegrity" : "sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH",
        "bsFrameCss" : "./css/bootstrap-common.css",
        "customCss" :
        [
            {
                "location" : "./css/fmda-form.css"
            }
        ],
        "infoHeading" : "dataInfo",
        "infoNumberOfItems" : 6,
        "copyrightYear" : "2025",
        "copyrightHolder" : "FileMaker Data API を使う",
        "bootstrapJs" : "https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js",
        "bsJsIntegrity" : "sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz",
        "customJs" :
        [
            {
                "location" : "./js/fmda-form-check.js"
            },
            {
                "location" : "./js/fmda-select-page.js"
            }
        ],
        "logoSvg" : "https://www.mlbstatic.com/team-logos/league-on-dark/1.svg",
        "logoSvgWidth" : "72"
    },
    "pageContents" : [
        {
            "heading" : "records - Create Record",
            "lead" : "Claris FileMaker Data API のリクエスト records - Create Record のテスト。フォームから PHP を実行して、FileMaker Server へリクエストを送信して新規レコードを作成します。"
        },
        {
            "heading" : "records - Edit Record",
            "lead" : "Claris FileMaker Data API のリクエスト records - Edit Record のテスト。フォームから PHP を実行して、FileMaker Server へリクエストを送信してレコードを更新します。"
        },
        {
            "heading" : "records - Delete Record",
            "lead" : "Claris FileMaker Data API のリクエスト records - Delete Record のテスト。フォームから PHP を実行して、FileMaker Server へリクエストを送信してレコードを削除します。"
        },
        {
            "heading" : "records - Duplicate Record",
            "lead" : "Claris FileMaker Data API のリクエスト records - Dupulicate Record のテスト。フォームから PHP を実行して、FileMaker Server へリクエストを送信して現在のレコードを複製します。"
        }
    ],
    "formContents" : [
        {
            "heading" : "チーム - レコード作成",
            "action" : "PHP_SELF",
            "buttonColor" : "btn-primary",
            "buttonText" : "新規レコード作成"
        },
        {
            "heading" : "チーム - レコード編集",
            "action" : "PHP_SELF",
            "buttonColor" : "btn-warning",
            "buttonText" : "レコード更新"
        },
        {
            "heading" : "チーム - レコード削除",
            "action" : "PHP_SELF",
            "buttonColor" : "btn-danger",
            "buttonText" : "レコード削除"
        },
        {
            "heading" : "チーム - レコード複製",
            "action" : "PHP_SELF",
            "buttonColor" : "btn-warning",
            "buttonText" : "レコード複製"
        }
    ],
    "infoItems" : [
        {
            "heading" : "ソリューション",
            "content" : "MLB サンプル"
        },
        {
            "heading" : "レイアウト",
            "content" : "standings"
        },
        {
            "heading" : "テーブル",
            "content" : "standings"
        },
        {
            "heading" : "テーブルのレコード数",
            "content" : "27"
        },
        {
            "heading" : "現在のレコード数",
            "content" : "27"
        },
        {
            "heading" : "取得したレコード数",
            "content" : "27"
        }
    ],
    "formControls" :
    [
        {
            "id" : "mlb_site_id",
            "label" : "MLB 公式サイト ID",
            "type" : "text",
            "name" : "mlb_site_id",
            "placeholder" : "100",
            "required" : "1",
            "disabled" : "",
            "feedback" : "MLB 公式サイトの チーム ID を入力してください。",
            "option" : [
                {
                    "value" : ""
                }
            ]
        },
        {
            "id" : "year",
            "label" : "シーズン",
            "type" : "text",
            "name" : "year",
            "placeholder" : "2024",
            "required" : "1",
            "disabled" : "",
            "feedback" : "シーズン（西暦）を入力してください。",
            "option" : [
                {
                    "value" : ""
                }
            ]
        },
        {
            "id" : "league",
            "label" : "リーグ",
            "type" : "select",
            "name" : "league",
            "placeholder" : "",
            "required" : "1",
            "disabled" : "",
            "feedback" : "リーグを選択してください。",
            "option" : [
                {
                    "value" : "アメリカン・リーグ"
                },
                {
                    "value" : "ナショナル・リーグ"
                }
            ]
        },
        {
            "id" : "division",
            "label" : "地区",
            "type" : "select",
            "name" : "division",
            "placeholder" : "",
            "required" : "1",
            "disabled" : "",
            "feedback" : "地区を選択してください。",
            "option" : [
                {
                    "value" : "東地区"
                },
                {
                    "value" : "中地区"
                },
                {
                    "value" : "西地区"
                }
            ]
        },
        {
            "id" : "team",
            "label" : "チーム名",
            "type" : "text",
            "name" : "team",
            "placeholder" : "",
            "required" : "1",
            "disabled" : "",
            "feedback" : "チーム名を入力してください。",
            "option" : [
                {
                    "value" : ""
                }
            ]
        },
        {
            "id" : "wins",
            "label" : "勝利数",
            "type" : "text",
            "name" : "wins",
            "placeholder" : "81",
            "required" : "1",
            "disabled" : "",
            "feedback" : "レギュラーシーズンの勝利数を入力してください。",
            "option" : [
                {
                    "value" : ""
                }
            ]
        },
        {
            "id" : "losses",
            "label" : "敗戦数",
            "type" : "text",
            "name" : "losses",
            "placeholder" : "81",
            "required" : "1",
            "disabled" : "",
            "feedback" : "レギュラーシーズンの敗戦数を入力してください。。",
            "option" : [
                {
                    "value" : ""
                }
            ]
        },
        {
            "id" : "winning_percentage",
            "label" : "勝率",
            "type" : "text",
            "name" : "winning_percentage",
            "placeholder" : ".500",
            "required" : "",
            "disabled" : "",
            "feedback" : "",
            "option" : [
                {
                    "value" : ""
                }
            ]
        },
        {
            "id" : "runs_scored",
            "label" : "得点",
            "type" : "text",
            "name" : "runs_scored",
            "placeholder" : "",
            "required" : "1",
            "disabled" : "",
            "feedback" : "レギュラーシーズンの得点を入力してください。",
            "option" : [
                {
                    "value" : ""
                }
            ]
        },
        {
            "id" : "runs_allowed",
            "label" : "失点",
            "type" : "text",
            "name" : "runs_allowed",
            "placeholder" : "",
            "required" : "1",
            "disabled" : "",
            "feedback" : "レギュラーシーズンの失点を入力してください。",
            "option" : [
                {
                    "value" : ""
                }
            ]
        },
        {
            "id" : "winning_percentage",
            "label" : "ピタゴラス勝率",
            "type" : "text",
            "name" : "pythagorean_expectation",
            "placeholder" : "",
            "required" : "",
            "disabled" : "",
            "feedback" : "",
            "option" : [
                {
                    "value" : ""
                }
            ]
        },
        {
            "id" : "expected_win_loss",
            "label" : "X-W/L",
            "type" : "text",
            "name" : "expected_win_loss",
            "placeholder" : "",
            "required" : "",
            "disabled" : "",
            "feedback" : "",
            "option" : [
                {
                    "value" : ""
                }
            ]
        },
        {
            "id" : "winning_percentage_diﬀerence",
            "label" : "勝率差分",
            "type" : "text",
            "name" : "winning_percentage_diﬀerence",
            "placeholder" : "",
            "required" : "",
            "disabled" : "",
            "feedback" : "",
            "option" : [
                {
                    "value" : ""
                }
            ]
        }
    ],
    "linkItems" : [
        {
            "pageIdentity" : 0,
            "formId" : "gotoCreateRecord",
            "anchorId" : "createRecord",
            "textContent" : "レコード作成"
        },
        {
            "pageIdentity" : 1,
            "formId" : "gotoEditRecord",
            "anchorId" : "editRecord",
            "textContent" : "レコード編集"
        },
        {
            "pageIdentity" : 2,
            "formId" : "gotoDeleteRecord",
            "anchorId" : "deleteRecord",
            "textContent" : "レコード削除"
        },
        {
            "pageIdentity" : 3,
            "formId" : "gotoDuplicateRecord",
            "anchorId" : "duplicateRecord",
            "textContent" : "レコード複製"
        }
    ]
}

```

### [fmda-form-check.js](https://qiita.com/RemixWorksLLC/items/df47f271da6f7693d0a4#fmda-form-checkjs)

### fmda-select-page.js
```
'use strict';

const createRecordForm = document.querySelector('#gotoCreateRecord');
const editRecordForm = document.querySelector('#gotoEditRecord');
const deleteRecordForm = document.querySelector('#gotoDeleteRecord');
const duplicateRecordForm = document.querySelector('#gotoDuplicateRecord');

const createRecord = document.querySelector('#createRecord');
const editRecord = document.querySelector('#editRecord');
const deleteRecord = document.querySelector('#deleteRecord');
const duplicateRecord = document.querySelector('#duplicateRecord');

createRecord.addEventListener('click', function( ) {
    createRecordForm.submit();
});
editRecord.addEventListener('click', function( ) {
    editRecordForm.submit();
});
deleteRecord.addEventListener('click', function( ) {
    deleteRecordForm.submit();
});
duplicateRecord.addEventListener('click', function( ) {
    duplicateRecordForm.submit();
});

```

## フッタのリンク部分

　フッタ部分は、`bs-builder-class.php` の `BsBuilder` クラス、スタティックメソッド `CreateFooter()` に記述されています。
 `id` 付きの `<a>` タグの前に、`type="hidden"` の form コントロールを持つ、フォームが仕込まれており、こちらにもそれぞれ `id` が設定されていて、ページの識別子と紐付けています。

  `id` は、`html-contents.json` の `linkItems` JSON配列内で設定されており、これを、`preferences.php` で、`$link_items` 配列として展開しています。

　これを `fmda-select-page.js` でクリックを監視。クリックされた id に基づいて、`name="page-identity"` の値を、POST で送信しています。

 これに、`index.php` の以下が反応し、ページを識別します。
 
```
@$page_indentity    = $_POST['page-identity'];
$page_indentity     = $page_indentity === null ? 1 : $page_indentity;
```

　これで、リクエスト機能なしのページは完成です。シングルページで、４つのフォームを表示できるようになりました。

　次回から、FileMaker Data API のリクエストを追加していきます。