メインコンテンツへスキップ
見出し画像

FileMakerでナレッジDBをつくるよ!Part 4

    前回から大分あいてしまいましたが、続きです✨

    ⏬️前回の記事

    前回はWebDirectで無事に表示でき、
    FileMakerではできたりできなかったりしたショートカットキーも
    問題なく使用できることが分かりました!

    また、データをFileMakerのDBに保存するように変更しようと
    ChatGPTに実装方法を尋ねたところで終わりました。

    今回はおまちかねの実装回です☺️



    実装

    FileMaker側の実装

    A. Webビューア計算式

    Master::EditorVersion (テキスト)フィールドを追加します。
    Ctrl + Shift + D でデータベースの管理から追加してください。

    画像
    追加したら「OK」で終了

    Webビューアの計算式を変更します。
    Ctrl + L でレイアウトモードに切り替えて、Webビューアをダブルクリックしてください。
    その後、「指定」をクリックしてください。


    画像

    上の計算式のなかに以下の計算式を貼り付けてください。
    (過去のものはコメントアウトで置いておきます)

    Let ([
    
      cacheKey = Master::EditorVersion ;
      serverHost = Get ( ホスト IP アドレス ) ;
      editorPath = "/editor/editor.html" ;
      
      URL = "http://" & serverHost & editorPath & "?v=" & cacheKey 
    ] ;
      URL
    )
    
     
    /* 2026/03/18 変更
    Let ([
    
      cacheKey = knowledgeDB::WebCacheKey ;
      serverHost = Get ( ホスト IP アドレス ) ;
      editorPath = "/editor/editor.html" ;
    
      contentB64 = Base64Encode ( TextEncode ( knowledgeDB::HTML_Content ; "UTF-8" ; 0 ) ) ;
      b64q = GetAsURLEncoded ( contentB64 ) ;
      
      URL = "http://" & serverHost & editorPath & "?v=" & cacheKey & "&b64=" & b64q
    ] ;
      URL
    )
    */

    後は、「OK」で画面を閉じていってください。
    Ctrl + B でブラウズモードに戻し、HTMLエディタが正常に表示されることを
    確認してください。

    ・・・

    B. レイアウト上の設定

    Webビューアのオブジェクト名を「wv_editor」にします。
    レイアウトモードにします。

    Webビューアを選択した状態で、右のところ(インスペクタといいます)の
    定規マークをクリックし、名前のところに「wv_editor」を入力してください。

    もしインスペクタが表示されていない場合は右上の赤枠の四角ボタンを
    クリックすると表示されます。

    画像

    ブラウザモードに戻り、セーブします。

    ・・・

    C. 開くスクリプト Editor_Open

    スクリプトを作成し、レコード表示時に実行するようにしましょう。

    スクリプトを作成するのは始めてでしたね。
    スクリプトというのは、FileMaker内で実行できるプログラムのようなものです。
    エクセルで動作するVBAのようなイメージです。

    違う点は、コードを手書きするのではなく、ステップと呼ばれる各動作を
    つなげていって作成する点です。

    計算式とスクリプトが作れれば、FileMakerを制したようなものです☺️

    # Editor_Open
    
    Set Variable [ $html ; Value: knowledgeDB::HTML_Content ]
    Set Variable [ $json ; Value: JSONSetElement ( "{}" ;
      [ "html" ; $html ; JSONString ] ;
      [ "recordId" ; Get ( レコードID ) ; JSONString ]
    ) ]
    
    Pause/Resume Script [ Duration (seconds): 0.2 ]
    
    Perform JavaScript in Web Viewer [ Object Name: "wv_editor" ; Function Name: "FMBridge.loadFromFM" ; Parameters: $json ]

    補足

    • Pause 0.2 は初期ロード待ちの簡易版です

    • もっと堅くするなら、editor 側から EditorReady を返してから本文投入にします

    これが以前ChatGPTに出してもらったスクリプトです。
    これを元にスクリプトを作っていきたいと思います。

    まず、「スクリプトの一時停止」(Pause 0.2)で実装し、
    動作検証の後、EditorReadyを返すようにします。

    まず、「スクリプト」→「スクリプトワークスペース」からスクリプトワークスペースを開きます。(Ctrl + Shift + S)

    画像

    左上の「新規スクリプト」をクリックすると新しいスクリプトを追加できます。
    「新規スクリプト」をクリックし、タイトルに「Editor_Open」と入力します。
    編集中のスクリプトはCtrl + S で保存できます。

    画像
    編集中の場合は 左のリスト及び上のタブのところに ★マークが付きます
    画像
    保存(Ctrl + S) すると★マークは消えます

    まず一行目

    Set Variable [ $html ; Value: knowledgeDB::HTML_Content ]

    これは変数設定のスクリプトステップです。
    右側のステップのところに「hen」と入力すると
    それに該当するステップが絞り込まれます。
    (「変数」と日本語でもOKです)

    今回だったら「変数を設定」を使うのでそれをダブルクリックすると左に挿入されます。
    今回は $html という変数に、knowledgeDB::HTML_Content フィールドの値を設定するようにします。

    行うことは非常にシンプルです。

    画像


    「変数を設定」のステップを選択した状態で、⚙️をクリックします。

    画像

    すると、下図のようなウインドウがでてきますので、

    名前:$html
    値:knowledgeDB::HTML_Content

    で設定してください。

    画像

    「OK」で設定完了です。

    画像
    Set Variable [ $html ; Value: knowledgeDB::HTML_Content ]

    日本語と英語でわかりにくいですが、上記2つは同じ意味合いになります。

    続いて、2ステップ目です。

    Set Variable [ $json ; Value: JSONSetElement ( "{}" ;
      [ "html" ; $html ; JSONString ] ;
      [ "recordId" ; Get ( レコードID ) ; JSONString ]
    ) ]

    同じく「変数を設定」のステップです。
    一度使ったステップはコピペができるので、エディタ上でコピペします。

    画像
    コピペ

    コピペをしたら2行目の値を変更します。

    名前:$json
    値: JSONSetElement ( "{}" ; [ "html" ; $html ; JSONString ] ; [ "recordId" ; Get ( レコードID ) ; JSONString ] )

    ⚙️をクリックします。

    画像

    値の「指定」をクリックします。
    そこに先程の値を貼り付けます。
    「OK」を押すとエラーがでました。

    画像
    画像
    画像

    Get 関数でエラーが起こっているようです。
    実は、「Get( レコード ID)」と間にスペースが必要です。
    ChatGPTはこういう手抜きをよくしてきます☺️

    もし分からなくても、右の検索窓に「Get」と入力すると、
    Get関数の一覧がでてきますのでそちらから選択も可能です。

    画像
    右の検索窓にGetと入れると、Get関数がズラリとでてきます

    Get関数は時刻や日付などを取得できる関数です。
    Get( レコード ID ) などよく使うものは覚えているのですが、
    時々しか使わないものは忘れがちです。
    その場合はChatGPTやWebで検索した方が早いかもしれません。
    (私は一時期全部覚えていました🫡笑)

    「Get( レコード ID)」に修正したので、これで「OK」がとおるはずです。


    💡Get( レコード ID ) とは?

    FileMakerはそれぞれのレコードに対して裏で連番の数字を持っています。
    これは書き換え不可能な数字でこのレコード固有の値になります

    画像

    無事、エディタに戻ることができました。

    Pause/Resume Script [ Duration (seconds): 0.2 ]

    次は一時停止のステップですね。
    普段は右の補助機能を使用せずに以下のようにしています。
    ・Enterキーで空の行を作成
    ・ステップを入れたいところでスペースを入力
    ・すると検索できるので入力する

    画像

    今回は「スクリプト一時停止/続行」がほしかったので、it と入力したら出てきました。
    ローマ字読みで入力するか、日本語でも検索ができます。

    画像

    「スクリプト一時停止/続行」のステップが挿入できたので、⚙️をクリックします。
    今回は、0.2 秒のWaitなので、以下のように設定します。

    画像

    「OK」でエディタに戻ってきました。
    現在、保存されていないので、★マークが付いていますが、
    細かな「保存」を心がけてください。
    (FileMakerは結構編集中にプロセスが落ちたりしますので⋯)

    画像

    次のステップはこちらですね。

    Perform JavaScript in Web Viewer [ Object Name: "wv_editor" ; Function Name: "FMBridge.loadFromFM" ; Parameters: $json ]

    「Web ビューアで JavaScript を実行」というステップです

    画像
    ja と入力すると出てきました
    画像
    これも ⚙️をクリックして値を設定します
    画像

    オブジェクト名:"wv_editor"
    関数名:"FMBridge.loadFromFM"
    引数:$json

    引数は、第一、第二と分けられるようで、
    左下のプラスボタンを押すと現れました。

    💡FileMaker:計算式での文字列

    FileMakerの計算式内で文字列を表したいときは、"(ダブルクォーテーション)で囲います。
    そのため、今回計算式に設定する文字列は "wv_editor" と入力する必要があります。

    ここまでで、ChatGPTの内容は全て終わりました。
    しかしエラー処理を追加したいと思います。

    画像
    これで、ChatGPTの推奨です。

    エラー処理追加

    画像
    選択のように追加しました。

    FileMakerには、「レイアウト」という画面を追加していくことができるのですが、それぞれのレイアウトにはベースとなる「テーブル」が存在します。
    今回は、「knowledgeDB」テーブルでのみ実施しないと
    予期せぬ動作になります。

    エラー処理として「knowledgeDB」テーブル以外では動作しないような処理を追加しました。

    ・・・

    TIP💡:FileMakerの「レイアウト」と「テーブル」の関係

    FileMakerを学び始めてつまずいたポイントです。
    少しややこしいので、作成だけされたい方は飛ばしていただければと思います!

    FileMakerの「レイアウト」には必ずベースになる「テーブル」が存在する仕様になっています。
    (不思議ですが)

    例えば、現在のレイアウト名は「knowledgeDB」で、テーブル名は「knowledgeDB」になっています。

    同じ名前なのでややこしいのですが、この2つは全くの別物です

    画像
    レイアウトモードにすると左上に現れます。

    レイアウトは画面毎に作られるので、下図のレイアウト管理画面のように同じ「knowledgeDB」テーブルがベースのレイアウトを複数作ることができます。

    画像
    レイアウトの管理画面ですが、「knowledgeDB」と「knowledgeDB コピー」という
    レイアウトがあることがわかります。

    今度は「データベース」の管理を見てみましょう。
    「knowledgeDB」と「Master」テーブルしかないことが分かります。

    画像

    「レイアウト:テーブル」 は 「0…n:1」の関係になっているのです。
    (テーブルがないとレイアウトは作られない関係です)
    「レイアウト」から見るとベースになる「テーブル」は必ず1つになります。

    次はリレーションシップを見てみましょう。

    画像

    Webビューアの計算式です。
    この中に「Master::EditorVersion」のフィールドを参照しているところがありました。

    Let ([
    
      cacheKey = Master::EditorVersion ;
      serverHost = Get ( ホスト IP アドレス ) ;
      editorPath = "/editor/editor.html" ;
      
      URL = "http://" & serverHost & editorPath & "?v=" & cacheKey 
    ] ;
      URL
    )

    Webビューアは「knowledgeDB」レイアウトにあり、「knowledgeDB」テーブルをベースにしています。

    この時、「knowledgeDB」レイアウトから「Master」テーブルを参照するということは、
    「knowledgeDB」テーブルから見て「Master」テーブルの値を見る
    ということなのです。

    どういう値が参照されるかは、「knowledgeDB」と「Master」の関係、
    すなわち、リレーションシップ
    によって値が変わってきます。

    今回は、「knowledgeDB」テーブルのレコードがどんなものでも、
    「Master」テーブルの1つのレコードを参照するように設計しています。

    ここはややこしいのですが、FileMakerのキモとなる部分になります。

    ・・・

    D. 保存受信用スクリプト SaveHTML

    こちらをスクリプト化していきます。

    # SaveHTML
    Set Variable [ $param ; Value: Get ( スクリプト引数 ) ]
    
    If [ Left ( $param ; 1 ) = "{" ]
      Set Variable [ $html ; Value: JSONGetElement ( $param ; "html" ) ]
    Else
      Set Variable [ $html ; Value: $param ]
    End If
    
    Set Field [ knowledgeDB::HTML_Content ; $html ]
    Commit Records/Requests [ With dialog: Off ]

    スクリプトワークスペース(Ctrl + Shift + S)を開き、
    新規スクリプトから「SaveHTML」を作成します。

    画像

    はい!できました!
    今回新しいのは、「フィールド設定」と「レコード/検索条件の確定」でしょうか。

    フィールド設定 はその名の通り、
    対象のフィールドに対して値を設定します。

    今回は「knowledgeDB」テーブルの「HTML_Content」フィールドに
    変数「$html」の値を設定しています。

    レコード/検索条件の確定 は フィールド設定 を行ったら実行するスクリプトステップです。

    レコードの確定を行わない状態で置いておくと、
    「レコード編集中」となり、他の人がそのレコードを編集できず、
    バグの温床になります。

    先程と同じ、「knowledgeDB」テーブル以外では動作しないようなエラー処理を追加します。
    スクリプトステップもコピペできるので、前回のスクリプトからコピペします。

    画像
    「Editor_Open」から該当箇所を選択し、コピー
    画像
    「SaveHTML」 の該当かよにペースト

    editor.html、bridge.js を所定の場所に配置する

    各ファイルの配置先は以下になります。

    http://<IPアドレス>/editor/editor.html
    http://<IPアドレス>/editor/bridge.js
    http://<IPアドレス>/editor/tinymce/tinymce.min.js

    editor.html

    <!doctype html>
    <html lang="ja">
    <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>TinyMCE Editor</title>
    
    <script src="./tinymce/tinymce.min.js"></script>
    <script src="./bridge.js"></script>
    
    <style>
      html, body {
        height: 100%;
        margin: 0;
        padding: 0;
        background: #fff;
        font-family: sans-serif;
      }
      #wrap {
        height: 100vh;
        display: flex;
        flex-direction: column;
      }
      #toolbar {
        padding: 8px;
        border-bottom: 1px solid #ddd;
        background: #f7f7f7;
        display: flex;
        gap: 8px;
        align-items: center;
      }
      #status {
        font-size: 12px;
        color: #666;
        margin-left: auto;
      }
      #editorArea {
        flex: 1;
        min-height: 0;
      }
      .tox-tinymce {
        border: 0 !important;
        border-radius: 0 !important;
        height: 100% !important;
      }
    </style>
    </head>
    <body>
    <div id="wrap">
      <div id="toolbar">
        <button type="button" onclick="FMBridge.saveToFM()">保存</button>
        <button type="button" onclick="FMBridge.reloadFromFM()">再読込</button>
        <span id="status">loading...</span>
      </div>
    
      <div id="editorArea">
        <textarea id="editor"></textarea>
      </div>
    </div>
    
    <noscript>
      このアプリケーションを使用するにはブラウザで JavaScript を有効にします。
    </noscript>
    
    <script>
    (function () {
      function setStatus(msg) {
        var el = document.getElementById('status');
        if (el) el.textContent = msg;
      }
    
      window.__fmEditorReady = false;
    
      tinymce.init({
        selector: '#editor',
        license_key: 'gpl',
        base_url: './tinymce',
        menubar: true,
        promotion: false,
        statusbar: true,
        height: '100%',
        plugins: [
          'lists', 'link', 'table', 'code', 'wordcount',
          'autolink', 'charmap', 'fullscreen', 'image'
        ],
        toolbar: 'undo redo | blocks | bold italic underline | alignleft aligncenter alignright | bullist numlist | link table | code fullscreen',
        setup: function (editor) {
          editor.on('init', function () {
            window.__fmEditorReady = true;
            setStatus('ready');
    
            // FileMaker に「ロード完了」を通知
            if (window.FileMaker && typeof window.FileMaker.PerformScript === 'function') {
              try {
                window.FileMaker.PerformScript('EditorReady', '');
              } catch (e) {}
            }
          });
    
          editor.on('change input undo redo setcontent', function () {
            FMBridge.dirty = true;
            setStatus('edited');
          });
        }
      });
    })();
    </script>
    </body>
    </html>

    bridge.js

    window.FMBridge = (function () {
      let lastLoadedHtml = '';
      let dirty = false;
    
      function getEditor() {
        return window.tinymce ? tinymce.get('editor') : null;
      }
    
      function safeJsonParse(value) {
        try {
          return JSON.parse(value);
        } catch (e) {
          return null;
        }
      }
    
      function waitEditor(callback, retry) {
        const maxRetry = retry || 50;
        let count = 0;
    
        function tick() {
          const ed = getEditor();
          if (ed && window.__fmEditorReady) {
            callback(ed);
            return;
          }
          count++;
          if (count < maxRetry) {
            setTimeout(tick, 100);
          }
        }
        tick();
      }
    
      function setStatus(msg) {
        const el = document.getElementById('status');
        if (el) el.textContent = msg;
      }
    
      function loadFromFM(param) {
        const data = typeof param === 'string' ? safeJsonParse(param) || { html: param } : (param || {});
        const html = data.html || '';
    
        waitEditor(function (ed) {
          ed.setContent(html);
          lastLoadedHtml = html;
          dirty = false;
          FMBridge.dirty = false;
          setStatus('loaded');
        });
      }
    
      function getHtml() {
        const ed = getEditor();
        if (!ed) return '';
        return ed.getContent();
      }
    
      function saveToFM() {
        const html = getHtml();
        const payload = JSON.stringify({
          html: html,
          dirty: dirty || FMBridge.dirty
        });
    
        if (window.FileMaker && typeof window.FileMaker.PerformScript === 'function') {
          window.FileMaker.PerformScript('SaveHTML', payload);
          lastLoadedHtml = html;
          dirty = false;
          FMBridge.dirty = false;
          setStatus('saved');
        } else {
          setStatus('FileMaker not found');
        }
      }
    
      function reloadFromFM() {
        if (window.FileMaker && typeof window.FileMaker.PerformScript === 'function') {
          window.FileMaker.PerformScript('EditorReloadRequest', '');
          setStatus('reload requested');
        }
      }
    
      return {
        dirty: false,
        loadFromFM,
        saveToFM,
        reloadFromFM,
        getHtml
      };
    })();

    サーバの以下のパスにある「editor.html」の内容を更新します。
    そして新たに「bridge.js」を作成し、中身を貼り付けてください。

    C:\Program Files\FileMaker\FileMaker Server\HTTPServer\conf\editor
    画像
    ファイルを配置して終わり!

    ・・・

    スクリプトトリガの設定

    これを設定したら一通り動作できるようになります。
    まず、動作検証できるところまでを目指します。

    • レイアウト入場時: Editor_Open

    • レコード移動時: Editor_Open

    • editor からの init 後: EditorReady

    ↓これらは、動作確認した後に堅牢化するときに実装します。

    ・editor からの init 後: EditorReady
    ・ただし、レコード移動中に未保存内容が消えるのを避けたいなら、
     切替前に保存確認を入れます。

    レイアウト系のスクリプトトリガは「レイアウト」→「レイアウト設定」
    から設定することができます。

    画像

    「スクリプトトリガ」タブを開きます。

    画像

    それぞれ、以下のスクリプトトリガになります。
    ・レイアウト入場時: Editor_Open →「OnLayoutEnter」

    画像

    ・レコード移動時: Editor_Open → 「OnRecordLoad」

    画像


    💡スクリプトトリガとは?

    スクリプトトリガとは、何かの動作をトリガーにして
    スクリプトを実行することです。
    例えば)
     ・レイアウトを開いたとき
     ・ファイルを一番最初に開いたとき
     ・フィールドの内容を変更したとき
    など、様々な動作の際にスクリプトを実行することができます。

    それぞれ、設定できる場所が違うので、
    急にトリガを設定してと言われても知らなかったらできないですよね…

    これで基本的に動くようになったはずです!

    今回、editor.htmlの内容を変えたので、「Master::EditorVersion」を更新しましょう。
    これでキャッシュをクリアすることができます。

    ・レイアウトモード(Ctrl + L)に切り替え
    ・左上の「レイアウト」より「Master」レイアウトを選択

    画像

    ・「挿入」→「フィールド設定」で「EditorVersion」を選択し「OK」

    画像

    ・追加されたフィールドを任意の場所に移し、ブラウズモード(Ctrl + B)に
    ・とりあえず、version「0.1」にしておきます

    画像

    動作確認やりますよ! & デバッグ

    ようやく動作確認できるようになりました。
    まず、「KnowledgeDB」レイアウトに切り替えます

    レイアウトに切り替えた途端、エラーですね。

    画像

    これは、editor.html で window.FileMaker.PerformScript('EditorReady', '');を
    実行し、「EditorReady」スクリプトがないので発生していると思われます。

            // FileMaker に「ロード完了」を通知
            if (window.FileMaker && typeof window.FileMaker.PerformScript === 'function') {
              try {
                window.FileMaker.PerformScript('EditorReady', '');
              } catch (e) {}
            }
    

    editor.htmlより抜粋

    スクリプトワークスペースを開き、空の「EditorReady」スクリプトを作成します。

    画像
    EditorReady スクリプトの中身は無し。

    これで、一度別レイアウトに切り替えて、
    「KnowledgeDB」レイアウトに戻ってみましょう。

    エラーは発生しなかったです。
    「保存」「再読込」ボタンも作成されていて、新しいHTMLが表示されていることが分かりますね☺️

    画像

    まずは保存から実行して見ましょう。
    とりあえず、文字を記載して保存します。


    画像
    この状態で「保存」ボタンを押すと、、、

    「保存」ボタンを押しても特に何も表示されていませんでした。

    この状態で「HTML_Content」に値があるか気になります。

    「ツール」→「データビューア」→「監視」→「+ボタン」で
    「knowledgeDB::HTML_Content」の値を見てみましょう!

    画像
    HTMLで「保存のテストです。」が保存されています!
    成功のように見えます☺️

    現在、1つ目のレコードに値を書きました。
    2つ目のレコードを作って、そこでも値を保存してみましょう。

    「レコード」→「新規レコード」をクリックします。
    今まで1レコードしかなかったですが、
    合計レコードが2に増えました!

    画像
    2レコードあります。

    この状態で保存を実行します。

    画像

    1つ前のレコードに戻ります。

    画像
    左にある「<」ボタンをクリック

    ちゃんとレコードに保存された値が表示されました!
    これで、基本的な動作確認はOKです。

    画像
    基本的にOK

    これは、レコードを切り替えたときに「Editor_Open」が実行され、

    画像
    レコードを切り替えると「Editor_Open」が実行される

    「HTML_Content」の内容が、JavaScript経由でHTMLエディタに渡されているのでこのような動作になります!

    画像

    まだまだ、動作検証は必要ですが、
    ・HTMLエディタで編集
    ・JavaScript経由でFileMakerのフィールドに値を保存
    ・フィールドの値をHTMLエディタに読み込み

    ここまではできることが分かりました!



    ちょっと長くなったので、今回はここまでにします!

    次回は、
    ・画像の保存ができるのか?
    ・WebDirectで動作するのか?
    を確認した後、今回ペンディングした堅牢化の処理を入れたいと思います。

    いちごいぬ🐶


    今回の生成物

    以下からダウンロードできます☺️
    ご利用前に、免責事項と利用条件をご確認ください。

    Tinymceは含まれていませんので、README.txtを確認し、
    同じフォルダ構造になるように配置してください。

    FileMakerのID/Password
    admin/admin

    免責事項
    本データは、個人または所属組織内での利用を前提として公開しています。著作権は作者に帰属します。
    無断での再配布、販売、改変後の配布・販売、作者の許可のない第三者提供は禁止します。
    本データは現状有姿で提供しており、動作保証・完全性・特定目的への適合性は保証しません。
    本データの利用または利用不能により生じた損害について、作者は一切の責任を負いません。
    同梱または関連する第三者ソフトウェアには、それぞれのライセンスが適用されます。


    ⏬️次回の記事



    #FileMaker #社内ナレッジ #ナレッジベース #ナレッジマネジメント #業務改善 #社内DX #情報整理 #ドキュメント管理 #FAQ #RAG #生成AI #ローカルLLM #ベクトルDB #セマンティック検索 #OCR #OneNote #Python #TinyMCE #JavaScript #WebViewer #WebView2 #DataAPI #FileMakerServer #WebDirect #システム設計 #保守運用 #内製開発 #検証ログ #連載企画



     
     
    「ちょっと疲れた心に、やさしいAIの居場所を。」 現代の孤独や生きにくさをAIの力で少しでも和らげたい。 そんな想いから、“ゆるこみ”プロジェクトを始動しました。 もし共感してもらえたら、固定記事を読んでみてください😊

    あなたへのおすすめ