2
Help us understand the problem. What are the problem?

posted at

updated at

FileMakerをJSで拡張して、画像付きリッチテキストドキュメントを作成、画像をドラッグでサイズ変更、JSの正規表現を使う。

この記事は、下記イベントに参加しています。
Qiita Engineer Festa 2022
https://qiita.com/official-campaigns/engineer-festa/2022
Claris FileMaker で作った App を JavaScript で拡張したらどうなる?!
https://qiita.com/official-events/0005a02478acfc50dd13

Claris FileMaker で作った App を JavaScript で拡張したら...

  • 画像付きのリッチテキストドキュメントが作成できます。
  • 画像をドラッグして表示サイズの変更ができます。
  • JavaScript の正規表現が使えます。
今回の記事を応用して、FileMakerの機能を組み合わせると、ドキュメントの管理はもちろん、ドキュメントに添付する画像やファイルの管理もできる、リッチテキストドキュメントエディタが作成できます。

Download: MarkdownThing

ココでは、FileMakerと WEBビューア,JavaScript との連携方法を簡略化して 説明します。

フィールドに記述したマークダウンをWEBビューアで表示。

edit_Markdown.png

動作検証

FileMaker Pro 19.5
Windows 10
macOS

サンプルファイル

画像付きリッチテキストドキュメントを作成、画像をドラッグしてサイズ指定

サンプルファイル: MdT.fmp12

テーブル

  • Mdt:ドキュメント(マークダウン)保存用
  • SourceCode:WEBビューア表示用ファイル(html,js,css)

Image 5.png

フィールド

  • _k :レコード固有識別子
  • markdown :マークダウンを保存
  • title :タイトル

Image 6.png

  • _k :レコード固有識別子
  • title :タイトル
  • url :ダウンロード元
  • code :ソースコード
  • fileName :WEBビューアで利用するファイル名
  • note :メモ
  • type :html,js,cssなど

Image 7.png

WEBビューアに表示するために使用する6ファイル

  • markdown.html (独自)
  • mdt.js (独自)
  • imgsize.css (独自)
  • marked.min.js (Open Source Software 以下OSS)
  • github-markdown.min.css (OSS)
  • moveable.js (OSS)
WEBビューアに表示するhtml
markdown.html
markdown.html
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, user-scalable=no">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="description" content="MarkdownThing">
    <title>MarkdownThing</title>

    <link rel='stylesheet' href='github-markdown.min.css'>
    <link rel="stylesheet" href="imgsize.css">
    <link rel="stylesheet" href="alerts.css">

    <script src="marked.min.js"></script>
    <script src="moveable.js"></script>
    <script src="mdt.js"></script>
</head>
<style>
    body {
        box-sizing: border-box;
        min-width: 200px;
        max-width: 980px;
        margin: 0 auto;
        padding: 30px 30px 10px 30px;
        /*上 右 下 左*/
    }
</style>
<body>
    <article class="markdown-body">
        <div id="$output" class="output">
        </div>
    </article>
    <script>
        const parseMarkdown = (markdown) => {
            $output.innerHTML = marked(markdown);
          /*moveable.js*/
           /*不要な 'moveable-control-box' 削除*/
           removeClassElement('moveable-control-box');
           moveableArrow();
        }

    /*WEBビューア レンダリング時、FileMaker.PerformScriptWithOption が使用可能になるまで待つ。*/
        const performScriptWithOption = (script, parameter, option, retry) => {
            retry = retry ? retry : 0;
            var numRetries = 5;
            try {
                FileMaker.PerformScriptWithOption(script, parameter, option);
            }
            catch (e) {
                if (retry < numRetries) {
                    setTimeout(function () {
                        performScriptWithOption(script, parameter, option, retry);
                    }, 500);
                }
                else {
                    location.reload();
                }
            }
            /*
            special thanks
            @TeraPicoData
            https://www.seedcode.com/web-viewer-tips-for-filemaker-19-hardening-the-filemaker-performscript-function/
            */
        };
        performScriptWithOption('OnWebViewerLoad[wv_Markdown]', '', '3', 0);
    </script>
</body>
</html>
Markdownレンダリング処理部分抜粋
        const parseMarkdown = (markdown) => {
            $output.innerHTML = marked(markdown);
        }
FileMakerから「Web ビューアで JavaScript を実行」で上記JS関数を実行し、Markdownのレンダリングを実行
Web ビューアで JavaScript を実行 [ オブジェクト名: "wv_Markdown"; 関数名: "parseMarkdown"; 引数 1: $markdown ]
独自JS moveable.js 用
mdt.js
mdt.js
/*moveableArrow{*/
const moveableArrow =()=>{
            /*Moveable{*/
            const moveable = new Moveable(document.body, {
                // If you want to use a group, set multiple targets(type: Array<HTMLElement | SVGElement>).
               //target: document.querySelector(".target"),

                scalable: true,
                keepRatio: true,
                throttleScale: 0,
                renderDirections: ["nw", "n", "ne", "w", "e", "sw", "s", "se"],
                edge: false,
                zoom: 1,
                origin: true,
                padding: { "left": 0, "top": 0, "right": 0, "bottom": 0 },
            });
            let frame = {
                translate: [0, 0],
                scale: [1, 1],
            };
            /*}Moveable*/
            /*Moveable{*/
            moveable.on("scaleStart", e => {
                e.set(frame.scale);
                e.dragStart && e.dragStart.set(frame.translate);
            }).on("scale", e => {

                const beforeTranslate = e.drag.beforeTranslate;

                frame.translate = beforeTranslate;
                frame.scale = e.scale;

                e.target.style.transform
                    = `translate(${beforeTranslate[0]}px, ${beforeTranslate[1]}px)`
                    + ` scale(${e.scale[0]}, ${e.scale[1]})`;


            }).on("scaleEnd", e => {
/*
                console.log(e);
                console.log("onScaleEnd", e.target, e.isDrag);
                console.log("onScaleEnd", e.target.getAttribute("src"), e.isDrag);
                console.log(e.target.getBoundingClientRect().width, e.target.getBoundingClientRect().height);
*/

                let src = e.target.getAttribute("src");

                let elements = Object.values(Array.from(document.body.querySelectorAll(`img[src='${src}']`)));

                let index = elements.indexOf(e.target);
                //console.log(elements.indexOf(e.target));

                let data = {
                    "index": index,
                    "src": e.target.getAttribute("src"),
                    "width": e.target.getBoundingClientRect().width,
                    "height": e.target.getBoundingClientRect().height
                }
                FileMaker.PerformScript('ResizeImage(json)', JSON.stringify(data));
                moveable.destroy();
                console.log(JSON.stringify(data));
            });



let elements = document.getElementsByTagName("img");
for (var $i = 0; $i < elements.length; $i++) {
    elements[$i].onclick = function () {
        //console.log("Clicked");
        //console.log(this.getBoundingClientRect());

/*moveable 枠ズレ対策*/
this.style.position='relative';

        moveable.setState({
            target: this,
        }, () => {
            //moveable.scaleStart(e);
        });
    }
}



            /*}Moveable*/
        }/*}moveableArrow*/

/*不要な 'moveable-control-box' を削除*/
let removeClassElement = (className) => {
  let elements = document.getElementsByClassName(className);
  for (let i = 0; i < elements.length; i++) {
    let e = elements[i];
    if (e) {
      e.parentNode.removeChild(e);
    }
  }
};

画像サイズ変更 ドラッグ終了時、画像情報を FileMaker.PerformScript で、FileMakerのスクリプト:ResizeImage(json) を実行
引数例: {"index":0,"src":"image.svg#=572x","width":526,"height":190}

抜粋
.on("scaleEnd", e => {
                let src = e.target.getAttribute("src");

                let elements = Object.values(Array.from(document.body.querySelectorAll(`img[src='${src}']`)));

                let index = elements.indexOf(e.target);
                //console.log(elements.indexOf(e.target));

                let data = {
                    "index": index,
                    "src": e.target.getAttribute("src"),
                    "width": e.target.getBoundingClientRect().width,
                    "height": e.target.getBoundingClientRect().height
                }
                FileMaker.PerformScript('ResizeImage(json)', JSON.stringify(data));
                moveable.destroy();
                console.log(JSON.stringify(data));
            });
独自CSS (画像サイズ指定用)

![](image.svg#=269x) #=269x (幅指定)または、 #=x269 (高さ指定)

imgsize.css
imgsize.css
img[src*='#=5x']	 {width:5px;}
img[src*='#=6x']	 {width:6px;}
img[src*='#=7x']	 {width:7px;}
/*...間は端折っています。Excelとかでドラッグすれば作れます。*/
img[src*='#=1998x']	 {width:1998px;}
img[src*='#=1999x']	 {width:1999px;}
img[src*='#=2000x']	 {width:2000px;}

img[src*='#=x5']	 {height:5px;}
img[src*='#=x6']	 {height:6px;}
img[src*='#=x7']	 {height:7px;}
/*...*/
img[src*='#=x1998']	 {height:1998px;}
img[src*='#=x1999']	 {height:1999px;}
img[src*='#=x2000']	 {height:2000px;}
Markdown レンダリングエンジン

marked.min.js (OSS) https://marked.js.org/
https://cdn.jsdelivr.net/npm/marked/marked.min.js

Markdown用スタイルシート

github-markdown.min.css (OSS)
https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/4.0.0/github-markdown.min.css

画像をドラッグでサイズ変更

moveable.js (OSS) https://github.com/daybrush/moveable
https://daybrush.com/moveable/release/latest/dist/moveable.min.js

WEBビューアに表示するためファイルをFileMakerのファイル内に格納

テーブル:SourceCode

table_SourceCode.png

レイアウト:SourceCode

各ファイルを以下のファイル名で登録

  • markdown.html
  • mdt.js
  • imgsize.css
  • marked.min.js
  • github-markdown.min.css
  • moveable.js

layout_SourceCode.png

ファイルを開く時の処理

  • WEBビューア 表示用ファイルパス設定
  • 各ファイルをエクスポート

Image 1.png
Image 2.png

WEBビューア 表示用ファイルパス設定

Win,Mac,iOS 用にファイルパスを設定

変数を設定 [$$pathHtmlMarkdownThing ; 値:
Let([
~path=Get ( テンポラリパス ) & "markdown.html"
;~os=Get (システムプラットフォーム)
];
 Case ( 
  ~os=1 ; ConvertFromFileMakerPath (~path ; URLPath)  ;
  ~os=-2 ; ConvertFromFileMakerPath (~path ; URLPath)  ;
  ~os=3 ; "file:/" & ConvertFromFileMakerPath (~path ; PosixPath) ;
  ConvertFromFileMakerPath (~path ; URLPath)
 )
)

スクリプト:Output_SourceCode でテンポラリフォルダへ各ファイルをエクスポート

「フィールド内容のエクスポート」では、エンコードが UTF-16 になるので、
「データファイルに書き込む」で、UTF-8 でエクスポート。

script_Output_SourceCode.png

Webビューアの設定

Image 3.png

markdown.html内

WEBビューア表示直後は、「FileMaker.PerformScriptWithOption」が実行できない場合があります。
以下で「FileMaker.PerformScriptWithOption」の実行エラーを防ぎます。

markdown.html内 JavaScript 抜粋
    /*WEBビューア レンダリング時、FileMaker.PerformScriptWithOption が使用可能になるまで待つ。*/
        const performScriptWithOption = (script, parameter, option, retry) => {
            retry = retry ? retry : 0;
            var numRetries = 5;
            try {
                FileMaker.PerformScriptWithOption(script, parameter, option);
            }
            catch (e) {
                if (retry < numRetries) {
                    setTimeout(function () {
                        performScriptWithOption(script, parameter, option, retry);
                    }, 500);
                }
                else {
                    location.reload();
                }
            }
            /*
            special thanks
            @TeraPicoData
            https://www.seedcode.com/web-viewer-tips-for-filemaker-19-hardening-the-filemaker-performscript-function/
            */
        };

Webビューアのレンダリング時にスクリプトを実行

Webビューアのレンダリング時に markdown.html内の以下のJavaScript で、「スクリプト:OnWebViewerLoad[wv_Markdown]」を実行し、フィールドのマークダウンをWebビューアに描画します。

Webビューアの読み込みをトリガとしてスクリプトを実行できる。

markdown.html内 JavaScript 抜粋
performScriptWithOption('OnWebViewerLoad[wv_Markdown]', '', '3', 0);

マークダウン レンダリング結果

Image 4.png

マークダウン内に以下を挿入して、印刷時の改ページを指定できます。

<!--改ページ--><div style='page-break-before:always'></div>

ドキュメントの幅を19mm(A4縦 余白10mm)に設定したい場合、
マークダウン内、先頭に以下を記述

<style>
body{
max-width: 718px;
}
</style>

画像をドラッグしてサイズ指定

BCCA722A-4F7D-B949-9514-0A29D46C4720.png

WEBビューア JavaScriptの処理

ドラッグ終了時、.on("scaleEnd", e => {... から、FileMaker.PerformScript('ResizeImage(json)', JSON.stringify(data)); で、スクリプトを実行。

引数例: {"index":0,"src":"image.svg#=572x","width":526,"height":190}

mdt.js 抜粋
.on("scaleEnd", e => {
//ドラッグ終了時
/*
                console.log(e);
                console.log("onScaleEnd", e.target, e.isDrag);
                console.log("onScaleEnd", e.target.getAttribute("src"), e.isDrag);
                console.log(e.target.getBoundingClientRect().width, e.target.getBoundingClientRect().height);
*/

                let src = e.target.getAttribute("src");

                let elements = Object.values(Array.from(document.body.querySelectorAll(`img[src='${src}']`)));
                //srcが同じ場合、indexで特定
                let index = elements.indexOf(e.target);
                //console.log(elements.indexOf(e.target));

                let data = {
                    "index": index,
                    "src": e.target.getAttribute("src"),
                    "width": e.target.getBoundingClientRect().width,
                    "height": e.target.getBoundingClientRect().height
                }
                FileMaker.PerformScript('ResizeImage(json)', JSON.stringify(data));
                moveable.destroy();
                console.log(JSON.stringify(data));
            });
mdt.js
/*moveableArrow{*/
const moveableArrow =()=>{
            /*Moveable{*/
            const moveable = new Moveable(document.body, {
                // If you want to use a group, set multiple targets(type: Array<HTMLElement | SVGElement>).
               //target: document.querySelector(".target"),

                scalable: true,
                keepRatio: true,
                throttleScale: 0,
                renderDirections: ["nw", "n", "ne", "w", "e", "sw", "s", "se"],
                edge: false,
                zoom: 1,
                origin: true,
                padding: { "left": 0, "top": 0, "right": 0, "bottom": 0 },
            });
            let frame = {
                translate: [0, 0],
                scale: [1, 1],
            };
            /*}Moveable*/
            /*Moveable{*/
            moveable.on("scaleStart", e => {
                e.set(frame.scale);
                e.dragStart && e.dragStart.set(frame.translate);
            }).on("scale", e => {

                const beforeTranslate = e.drag.beforeTranslate;

                frame.translate = beforeTranslate;
                frame.scale = e.scale;

                e.target.style.transform
                    = `translate(${beforeTranslate[0]}px, ${beforeTranslate[1]}px)`
                    + ` scale(${e.scale[0]}, ${e.scale[1]})`;


            }).on("scaleEnd", e => {
//ドラッグ終了時
/*
                console.log(e);
                console.log("onScaleEnd", e.target, e.isDrag);
                console.log("onScaleEnd", e.target.getAttribute("src"), e.isDrag);
                console.log(e.target.getBoundingClientRect().width, e.target.getBoundingClientRect().height);
*/

                let src = e.target.getAttribute("src");

                let elements = Object.values(Array.from(document.body.querySelectorAll(`img[src='${src}']`)));
                //srcが同じ場合、indexで特定
                let index = elements.indexOf(e.target);
                //console.log(elements.indexOf(e.target));

                let data = {
                    "index": index,
                    "src": e.target.getAttribute("src"),
                    "width": e.target.getBoundingClientRect().width,
                    "height": e.target.getBoundingClientRect().height
                }
                FileMaker.PerformScript('ResizeImage(json)', JSON.stringify(data));
                moveable.destroy();
                console.log(JSON.stringify(data));
            });



let elements = document.getElementsByTagName("img");
for (var $i = 0; $i < elements.length; $i++) {
    elements[$i].onclick = function () {
        //console.log("Clicked");
        //console.log(this.getBoundingClientRect());

/*moveable 枠ズレ対策*/
this.style.position='relative';

        moveable.setState({
            target: this,
        }, () => {
            //moveable.scaleStart(e);
        });
    }
}



            /*}Moveable*/
        }/*}moveableArrow*/

/*不要な 'moveable-control-box' を削除*/
let removeClassElement = (className) => {
  let elements = document.getElementsByClassName(className);
  for (let i = 0; i < elements.length; i++) {
    let e = elements[i];
    if (e) {
      e.parentNode.removeChild(e);
    }
  }
};

FileMaker スクリプトの処理

Image 8.png

スクリプト:ResizeImage(json)
//# 引数 例
//# {"index":0,"src":"AB9E06A0-B91F-A147-806C-925674A84FC4#=150x","width":151.50021362304688,"height":151.50021362304688}
変数を設定 [ $arg; 値:Get(スクリプト引数) ]
If [ IsEmpty ( $arg ) ]
現在のスクリプト終了 [ ]
End If
変数を設定 [ $json; 値:$arg ]
//{"index":0,"src":"https://fm-aid.com/markdownthing/files/93A9C5BF-BE9B-1C44-9217-C5E8828EA0BC.jpg#=497x","width":428.52166748046875,"height":298.529052734375}
変数を設定 [ $index; 値:JSONGetElement ( $json ; "index" ) ]
変数を設定 [ $src; 値:JSONGetElement ( $json ; "src" ) ]
変数を設定 [ $width; 値:JSONGetElement ( $json ; "width" ) ]
変数を設定 [ $height; 値:JSONGetElement ( $json ; "height" ) ]

//# Path 日本語 対応
//# $srcに日本がある場合、URLエンコードされて返ってくるので、デコードする
//# $src = img%E3%82%A4%E3%83%A1%E3%83%BC%E3%82%B8.jpg#=202x
変数を設定 [ $srcStartPosition; 値:Position ( MdT::markdown ; $src ; 1 ; $index+1 ) ]
If [ $srcStartPosition=0 ]
  変数を設定 [ $srcURLPath; 値:ConvertToFileMakerPath ( $src ; URLPath) ]
  If [ $srcURLPath="?" ]
    変数を設定 [ $src; 値:ConvertToFileMakerPath ( "file:/" & $src ; URLPath) ]
  Else
    変数を設定 [ $src; 値:$srcURLPath ]
  End If
 変数を設定 [ $src; 値:Substitute ( $src ; "file:/" ;  ) ]
 変数を設定 [ $src; 値:GetValue ( $src ; 2 ) ]
 変数を設定 [ $src; 値:Substitute ( $src ; Char ( 32 ) ; "%20" ) ]
//# デコード結果
//# $src = imgイメージ.jpg#=202x
End If

変数を設定 [ $srcStartPosition; 値:Position ( MdT::markdown ; $src ; 1 ; $index+1 ) ]
If [ $srcStartPosition=0 ]
  スクリプト実行 [ Parse_Markdown[Timer](sec); 引数: .25 ]
  現在のスクリプト終了 [ ]
End If
変数を設定 [ $srcLength; 値:Length ( $src ) ]
If [ PatternCount ( $src ; "#=x" ) ]
  #height
  変数を設定 [ $newSize; 値:"#=x" & Round ( $height ; 0 ) ]
Else
  #width
  変数を設定 [ $newSize; 値:"#=" & Round ( $width ; 0 ) & "x" ]
  //# $newSize = "#=152x"
End If
変数を設定 [ $newSrc; 値:GetValue ( Substitute ( $src ; "#" ;  ) ; 1 ) & $newSize ]
//# $newSrc = "imgイメージ.jpg#=152x"
//# Replace で、$src を $newSrc へ置換
フィールド設定 [ MdT::markdown; Replace ( MdT::markdown ; $srcStartPosition ; $srcLength ; $newSrc ) ]
レコード/検索条件確定 [ ダイアログなし ]
//# マークダウン 描画
スクリプト実行 [ Parse_Markdown[Timer](sec); 引数: .25 ]

正規表現

サンプルファイル: wvRegex.fmp12

Image 3.png

WEBビューアを設置

オブジェクト名:wv_forScript
※例ではアイコン表示のためにhtmlを記述していますが、空っぽでもOK。
Image 4.png

Image 5.png

FileMaker Script : Run_wvRegex

Image 2.png

FileMaker Script : Run_wvRegex
変数を設定 [ $js; 値:"((str)=>{ let result = str.match(" & wvRegex::pattern & "); FileMaker.PerformScriptWithOption ('fmBridgIt.Return', JSON.stringify(result), 5); })" ]
Web ビューアで JavaScript を実行 [ オブジェクト名: "wv_forScript"; 関数名: $js; 引数 1: wvRegex::str ]
変数を設定 [ $result; 値:Get(スクリプトの結果) ]
フィールド設定 [ wvRegex::result; $result ]
上記のJavaScript部分
((str)=>{
 let result = str.match(" & wvRegex::pattern & ");
 FileMaker.PerformScriptWithOption ('fmBridgIt.Return', JSON.stringify(result), 5); 
})

FileMaker Script : fmBridgIt.Return

Image 1.png

スクリプト実行の流れ

Image 6.png

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
2
Help us understand the problem. What are the problem?