この記事は、下記イベントに参加しています。
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ビューアで表示。
動作検証
FileMaker Pro 19.5
Windows 10
macOS
サンプルファイル
画像付きリッチテキストドキュメントを作成、画像をドラッグしてサイズ指定
サンプルファイル: MdT.fmp12
テーブル
- Mdt:ドキュメント(マークダウン)保存用
- SourceCode:WEBビューア表示用ファイル(html,js,css)
フィールド
- _k :レコード固有識別子
- markdown :マークダウンを保存
- title :タイトル
- _k :レコード固有識別子
- title :タイトル
- url :ダウンロード元
- code :ソースコード
- fileName :WEBビューアで利用するファイル名
- note :メモ
- type :html,js,cssなど
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
<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>
const parseMarkdown = (markdown) => {
$output.innerHTML = marked(markdown);
}
Web ビューアで JavaScript を実行 [ オブジェクト名: "wv_Markdown"; 関数名: "parseMarkdown"; 引数 1: $markdown ]
独自JS moveable.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
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
レイアウト:SourceCode
各ファイルを以下のファイル名で登録
- markdown.html
- mdt.js
- imgsize.css
- marked.min.js
- github-markdown.min.css
- moveable.js
ファイルを開く時の処理
- WEBビューア 表示用ファイルパス設定
- 各ファイルをエクスポート
WEBビューア 表示用ファイルパス設定
Win,Mac,iOS 用にファイルパスを設定
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 でエクスポート。
Webビューアの設定
markdown.html内
WEBビューア表示直後は、「FileMaker.PerformScriptWithOption」が実行できない場合があります。
以下で「FileMaker.PerformScriptWithOption」の実行エラーを防ぎます。
/*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ビューアの読み込みをトリガとしてスクリプトを実行できる。
performScriptWithOption('OnWebViewerLoad[wv_Markdown]', '', '3', 0);
マークダウン レンダリング結果
マークダウン内に以下を挿入して、印刷時の改ページを指定できます。
<!--改ページ--><div style='page-break-before:always'></div>
ドキュメントの幅を19mm(A4縦 余白10mm)に設定したい場合、
マークダウン内、先頭に以下を記述
<style>
body{
max-width: 718px;
}
</style>
画像をドラッグしてサイズ指定
WEBビューア JavaScriptの処理
ドラッグ終了時、.on("scaleEnd", e => {...
から、FileMaker.PerformScript('ResizeImage(json)', JSON.stringify(data)); で、スクリプトを実行。
引数例: {"index":0,"src":"image.svg#=572x","width":526,"height":190}
.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));
});
/*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 スクリプトの処理
//# 引数 例
//# {"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
WEBビューアを設置
オブジェクト名:wv_forScript
※例ではアイコン表示のためにhtmlを記述していますが、空っぽでもOK。
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 ]
((str)=>{
let result = str.match(" & wvRegex::pattern & ");
FileMaker.PerformScriptWithOption ('fmBridgIt.Return', JSON.stringify(result), 5);
})