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

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

    ⏬️前回の記事

    前回は以下まで進みました。

    • FileMaker Serverへの移行(Data APIはどれくらい使える?)

    • WebDirectなら画面が貼り付けられるのか?

    • WebブラウザからFileMakerへの書き込み&読み込み(Data API)

    今回はWebDirectでどのような操作ができるのか?を確認していきたいと思います。


    ご報告

    その前に1つご報告があります。
    前回の記事でFileMakerのHTMLエディタでは画像を貼り付けることができないとお伝えしていたのですが、
    本日確認すると貼り付けることができました!

    画像
    やったー!

    原因なのですが、
    今まで画像を貼り付ける時に Ctrl + V で貼り付けていました。
    FileMakerの場合、なぜかショートカットキーが動作しないときがあります。
    右クリック貼り付けで貼り付けることができました。
    しかし、ショートカットが使えないと面倒なので、
    これも原因を考える必要があります。


    WebDirectで参照できるのか?

    まずは、このファイルをWebDirectで公開してみて、
    どのように表示されるのか。画像のコピーやショートカットは使用できるのか確認してみたいと思います。

    FileMaker Serverの設定

    下図のようにWeb公開を有効にする必要があります。

    画像
    • 「Claris FileMaker WebDirect」の「FileMaker WebDirect」を 有効

    • 「Web 公開エンジン」の「プライマリマシン」を 有効

    上記設定でWebDirectは使用可能になります。
    「プライマリマシン」を有効にするにはFileMaker ServerのバージョンにあったJDKをインストールする必要があります。

    ・・・

    ファイル側の設定

    画像

    ユーザ毎にWebDirectで開けるか設定することができます。
    ファイル → 管理 → セキュリティ を開いてください。
    すると下図の「セキュリティの管理」画面が表示されます。

    画像

    左下の「詳細設定」をクリックしてください。

    画像

    今回は Admin が所属している「完全アクセス」のアクセス権セットを編集します。
    「完全アクセス」を選択し、左下から2番目の「編集」ボタンをクリックしてください。

    画像

    左下の拡張アクセス権にある「FileMaker WebDirectによるアクセス(fmwebdirect)」は最初は空なので、チェックを入れます。

    全てのウインドウを「OK」で閉じます。

    これでWebDirectで開けるはずです。

    ・・・

    テスト

    下記のURLでWebDirectを実行することができます。

    http://<サーバ名>/fmi/webd/knowledgeDB

    実行するアカウント名とパスワードを聞いてきたので、
    admin/adminでログインします。

    画像
    なにがでるか??

    無事にWebDirectで表示することができました!

    画像

    しかし、肝心なWebビューアのところが「<サーバIP> 接続が拒否されました」となり動きません。

    ・・・

    デバッグ

    まずはどのようなエラーが発生しているのかを見てみます。
    Edgeですと、「F12」で管理者ツールを起動できます。
    コンソールを開いてみると以下のようなエラーがでてました。

    画像

    http://<IPアドレス>/ 側が X-Frame-Options: SAMEORIGIN を返していて、WebDirect の親ページと同一オリジンではないため、iframe 内表示を拒否されています。
    その結果、ブラウザはエラーページ chrome-error://chromewebdata/ に落ち、そこから editor.html を読もうとしても、今度は chrome-error://... と http://<IPアドレス>/... が別オリジンなのでさらに失敗しています。

    http と https の混在や、
    呼び出しが「http://<IPアドレス>/」と「http://<ホスト名/」など不一致で起こるとのことでした。

    待てよ! 今回、実行URLを<サーバ名>で呼び出していました。

    http://<サーバ名>/fmi/webd/knowledgeDB

    WebビューアからはIPアドレスでアクセスしています。
    であれば、実行URLをIPアドレスに変えればうまくいくのでは?

    http://<IPアドレス>/fmi/webd/knowledgeDB


    無事、WebDirectで表示できました!!

    画像

    しかも、FileMakerではショートカットキーが効かないことがある
    バグなのかエラーがありましたが、
    WebDirectではそれがなく動作しました!
    これはWebDirectでナレッジ入力・編集することになりそうです☺️


    WebブラウザからFileMakerへの書き込み&読み込み(Data API)

    現状ではHTMLブラウザにただ記載しているだけなので保存されていません。それをJavaScript経由でFileMakerのDBに書き込めるように修正していきたいと思います。
    またページを開いたときには、保存されていた情報を読み込み、
    内容を編集できるようにします。

    FileMakerにはData APIというデータやり取りのAPIがあるので、
    それを使用し実装する方針です。

    ・・・



    全体構成について

    1. サーバ配置

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

    2. FileMaker 側

    • Webビューア名: wv_editor

    • HTML保存フィールド: knowledgeDB::HTML_Content

    • キャッシュキー: Master::EditorVersion

    • 初期化用スクリプト: Editor_Open

    • 保存受信用スクリプト: SaveHTML

    3. 動き

    1. レイアウト表示時に Webビューアは固定URLを開く

    2. ロード完了後、FileMaker から JavaScript を実行して本文を渡す

    3. ユーザーが保存ボタンを押す

    4. FileMaker.PerformScript("SaveHTML", html) で返す

    5. FileMaker がフィールド保存


    FileMaker 側の実装

    A. Webビューア計算式

    まずは 本文をURLに載せない 形にします。

    Let ( [
      v = Master::EditorVersion
    ] ;
      "http://<IPアドレス>/editor/editor.html?v=" & v
    )
    

    ポイント

    • knowledgeDB::HTML_Content は URL に載せない

    • v は HTML や JS を更新したときだけ変える

    ・・・


    B. レイアウト上の設定

    Webビューアのオブジェクト名をこれにしてください。

    wv_editor
    

    そして Webビューア設定で
    「JavaScript による FileMaker スクリプトの実行を許可」 をオンにします。
    これがないと FileMaker.PerformScript() が使えません。


    C. 開くスクリプト Editor_Open

    レコード表示時、または対象フィールドに入ったときに実行します。

    # 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 を返してから本文投入にします


    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 ]
    

    E. もっと安定させる保存受信

    必要なら dirty 管理もできます。

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

    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
      };
    })();
    

    追加で作ると安定するスクリプト

    EditorReady

    editor 側のロード完了通知です。
    これを使うと Pause 0.2 を減らせます。

    # EditorReady
    Set Variable [ $html ; Value: knowledgeDB::HTML_Content ]
    Set Variable [ $json ; Value: JSONSetElement ( "{}" ;
      [ "html" ; $html ; JSONString ]
    ) ]
    
    Perform JavaScript in Web Viewer [ Object Name: "wv_editor" ; Function Name: "FMBridge.loadFromFM" ; Parameters: $json ]
    

    EditorReloadRequest

    editor 側の再読込ボタン用です。

    # EditorReloadRequest
    Set Variable [ $html ; Value: knowledgeDB::HTML_Content ]
    Set Variable [ $json ; Value: JSONSetElement ( "{}" ;
      [ "html" ; $html ; JSONString ]
    ) ]
    
    Perform JavaScript in Web Viewer [ Object Name: "wv_editor" ; Function Name: "FMBridge.loadFromFM" ; Parameters: $json ]
    

    推奨トリガ

    おすすめはこうです。

    • レイアウト入場時: Editor_Open

    • レコード移動時: Editor_Open

    • editor からの init 後: EditorReady

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


    今回の構成の利点

    1. URL長制限を回避
    本文を URL に載せないので長文でも安定です。

    2. WebDirect でも安定しやすい
    same-origin で固定URL運用しやすいです。WebDirect の Webビューアは iframe 制約を受けるため、この設計が有利です。(help.claris.com)

    3. エディタ側と FileMaker 側の責務が分かれる

    • editor.html: 表示と編集

    • bridge.js: 橋渡し

    • FileMaker: 保存と読込


    実装時の注意

    1. GetLayoutObjectAttribute(...;"content")
    WebDirect では Webビューアの content と source が URL 扱いになる既知差分があるため、今回のような Perform JavaScript in Web Viewer 主体 の方が扱いやすいです。

    2. target=_top のような遷移は避ける
    WebDirect の親画面を飛ばすことがあります。

    3. 外部ソースは same-origin で
    release notes でも same-origin を外す外部ソースで WebDirect 側の問題が出た記述があります。


    まずの導入順

    1. 今の b64 付きURLをやめて固定URLに変更

    2. editor.html と bridge.js をサーバへ配置

    3. SaveHTML と EditorReady を作成

    4. Webビューア設定で FileMaker スクリプト実行許可をオン

    5. レイアウト起動時に Editor_Open 実行

    6. FileMaker Pro と WebDirect で保存往復確認


    今回は長くなってしまったので、ここで終了としたいと思います!
    次回は、今回の内容を実装して動作するかを確認したいと思います☺️

    いちごいぬ🐶


    ⏬️次回の記事


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



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

    あなたへのおすすめ