electronでkintoneのフォームブリッジを呼び出し操作する
素のkintoneは、フォームなどからレコード追加時にメールでお知らせがひどくショボいので、Form Bridgeを利用してフォーム送信時に管理者にメール通知をするようセットしてあります(送信先はTeamsのメールアドレス宛)。
このForm Bridgeは外部サイト等に埋め込めるようにiframeのタグなどを出力してくれるのですが、そのままだと本当に埋め込むだけで、尚且electronのようなアプリから連動もできません(カスタマイズも出来ない)そこで、今回electronで使えるようにする為にいくつか取り組んでみました。
※但しこの方法、値を代入してもformブリッジ側の仕様で送信しようとすると、値が入っていても入っていないと言われるので、この問題はPuppeteerで解決することにしました。
目次
今回使用する機能
- electronのwebview機能(iframeのようなものだけれど色々操作が可能)
今回は、form bridgeのフォームをelectron内で表示させ、社員IDや社員名、メールアドレスなどを予め設定してあるデータから自動挿入して送れるようにするのが目的です。
こちらの方法を使えば、単独では素のHTMLでform bridgeをカスタマイズ可能な状態で使えるのですが、この方法だと
- Bot対策をオンにしていると動かない
- 多言語対応していると動かない
- BrwoserWindowのnodeIntegrationがtrueだと動かない
- 3.でfalseにした場合、動くものの送信時にCSRFの問題で送信が出来ない。単純なformとinputで出来てるわけじゃないみたい。
- 直接BrowserWindowのloadurlでiframe内のURLを指定すれば動くものの、カスタマイズが出来ない
といった具合で、正直electronで操作するには非常に辛い。ので、今回はwebviewで呼び出して操作する方式を取りました。
事前準備
今回の事前準備として、Form Bridge側の設定を行います。
自動返信メール
自動返信メールの設定を行っておきます。送信者に対して入力内容の証左としてメールを送るようにしてあげます。HTMLメールとし、本文に差し込む形で入力内容を配置しておきます。
図:自動応答はもはやフォームの基本
回答後処理
回答後処理の上記の自動応答の後に、管理者に対しても通知を行います。メールですが送られる度にメールが飛んでこられても迷惑なので、メールの送信先をMicrosoft365のTeamsのチャンネルに設定してあるメアドに対して送ります。上記のようにHTMLとして送ることで、Teams側に対して通知が来るので、チームメンバーの誰かが、タスクをピックアップする事が可能になります。
図:こんな感じでフローを構築する
図:Teamsに投稿内容が飛んでくる
iframeのURLを取り出す
最後にelectron側で呼び出すURLを取得します。form bridge編集を完了後に、右上にあるiframe用の埋め込みコードをクリックして取るのですが、この中に入ってるURLを利用します。
iframe内でsrcで指定されているURLがそれになります。これを使いますのでコピーしておきましょう。
図:iframeの中にあるURLが呼び出し対象
ソースコード
今回は、nodeIntegrationをfalse(electron5.0以降はこれがデフォルト)のままで、レンダラー側でIPC通信出来るように、preload指定等を入れています。またwebviewを利用する為、メインプロセス、レンダラープロセスに加えて、webviewプロセスが登場するため、webviewプロセスとレンダラプロセス側で通信を出来るようにする必要があります。
Electron v12より
Electron v12より、remoteの廃止およびpreload.jsの使用がデフォルトとなった為、以下に記述の方法では通信ができません。IPC通信の呼び出し方が変わっているので、以下のエントリーを参考に書き直す必要性があります。
メインプロセス側
index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
function questwindowopen(){ // メイン画面の表示。ウィンドウの幅、高さを指定できる questWindow = new BrowserWindow({ 'width': 950, 'height': 750, 'autoHideMenuBar':true, //nodeIntegrationを有効にしないとrenderProcessでrequireを使えない。v5.0.0ではデフォルトで廃止 webPreferences: { nodeIntegration: false, contextIsolation: false, preload: __dirname + '/preload.js', webviewTag: true }, 'resizable':true, 'fullscreenable':false, 'fullscreen':false, 'alwaysOnTop':false, 'modal':true, 'parent': mainWindow //モーダル表示にしておく }); //questWindow.toggleDevTools(); //初期ページの表示 questWindow.loadURL('file://' + __dirname + '/form.html'); questWindow.on('closed', function() { electron.session.defaultSession.clearCache(() => {}) questWindow = null; }); // 全てのウィンドウが閉じたときの処理 app.on('window-all-closed', () => { //なにもしない //これをいれておかないと、全部のウィンドウが閉じられるとアプリまで閉じてしまう、。 }); } |
- いつもの新しいbrowserWindow生成時コードですが、webPreferencesに少しオプションを加えています。
- nodeIntegrationはfalseにします。
- contextIsolationはfalseにします。これをオフにしないとpreloadスクリプトを使ってプロセス間で情報共有できない。
- preloadでは、使用するpreload用JSファイルを指定。これがnodeIntegrationをtrueにした時の現在の代替策です。
- webviewTagはtrueにします。これでiframeのように自分のHTML内に外部HTMLを呼び込めます。
preload.js
1 2 3 4 5 |
window.ipcRenderer = require('electron').ipcRenderer; window.remote = require('electron').remote; window.addEventListener('load', () => { window.$ = window.jQuery = require('jquery'); }); |
- 今回は、ipcRendererを使えるようにセット。
- これで、レンダラプロセス側で、window.ipcRendererでこれまでのrequireと同じ事が出来るようになります。
- これを入れておかないと、nodeIntegrationがfalseであるため、レンダラプロセス側でrequireは使えなくなってるので、Node.js側と通信が出来ません。
- jQueryをnpmで入れている場合には、addEventListnerのloadでjqueryライブラリを読み込みさせることが可能です。CDNのURL書きでも可。
レンダラプロセス側
form.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
<!DOCTYPE html> <html> <head> <title>IT問い合わせ申請フォーム</title> <style> ::-webkit-scrollbar { background-color: #fff; width: .8em display:none; } ::-webkit-scrollbar-thumb:window-inactive, ::-webkit-scrollbar-thumb { background: black display:none; } </style> </head> <body> <div style="width:100%; height:100%"> <webview src="https://form.kintoneapp.com/public/form/show/formの番号をここに入れる?iframe=true" border="0" autosize="on" style="min-width:850px; min-height:800px" preload="injector.js" id="wv"> </webview> </div> <script> //メインプロセスとIPC通信を行う var ipcRenderer = window.ipcRenderer; //webviewを取得する const webview = document.querySelector('webview') //webview読み込み時イベント(webviewプロセスとIPC通信を行う) webview.addEventListener('dom-ready', () => { //意味もなくwebview側ページタイトルを取得してみる let titlePage = webview.getTitle(); //webview側にCSSを手動適用 webview.insertCSS('html,body{ background-color: #d3ffcc !important;}') //webview側のデベロッパーツールを開く webview.openDevTools(); //webview側へデータを送る webview.send("changeman",{ id: "社員ID", name: "苫東太郎", mail: "tomato@gmail.com" }); }) </script> </body> </html> |
- webviewに高さと幅を入れただけでは反応しないので、外側にdivを1個入れてサイズ指定しています。
- webviewタグのsrcにはForm bridgeのiframeの中で指定されてるURLを使用します。
- webviewタグのpreloadには、レンダラプロセスとwebviewプロセス間で通信するためのJSファイルを指定します。
- addEventListnerではdom-readyがwebviewタグ読み込み時イベントになりますので、追加しておきます。
- webview側のconsole.logはelectron側ではそのままでは表示されないので、別途openDevToolsにて開かせることが可能です。
- webview側のchangemanに対して3つの引数を渡しています。
- これで読み込み完了後のフォームに、社員番号、氏名、メールアドレスが自動で入るので、メインプロセス側の例えばMySQLから引っ張ってきた情報を橋渡ししてフォームに自動入力可能です。
- webview.insertCSSにてwebview側に対してカスタムCSSを適用可能です。
injector.js
1 2 3 4 5 6 7 8 9 10 11 12 13 |
const { ipcRenderer } = require('electron') ipcRenderer.on("changeman",function(event,data){ //FormにNAME属性を追加する var forms = document.forms[0].setAttribute("name","kinoko"); //社員番号、社員名、メールアドレスに値を入れる document.kinoko.elements[0].value = data.id; document.kinoko.elements[1].value = data.name; document.kinoko.elements[2].value = data.mail; //詳細入力欄の高さを変更 document.kinoko.elements[5].style.height = "300px"; }); |
- レンダラプロセスとwebviewプロセスとの間で、IPC通信を確立する為に使います。
- 例えば、レンダラプロセス側でchangemanをwebview.send("changeman",引数)で送ると、webviewの中に対して影響を与える事が可能です
- 逆に、webviewプロセス側からレンダラプロセス側にデータを送る場合には、ipcRenderer.sendToHostで同じような事が可能。レンダラプロセス側は、ipc-messageイベントにてこれをキャッチすることが出来ます。
- 上記のipc-messageイベントはwebviewに対してaddEventListnerでイベント割当をしなければなりません。その場合の事例は以下の通り
- 下記の事例の場合、injector.jsから送る際には、ipcRenderer.sendToHost("tomato",引数)で送れます。
- form bridge内のformはname属性がついていないので、追加しています。
- その中にある各種elementに対して、レンダラプロセス側から受け取った引数を、流し込んでいます。
- webview内に対してCSSカスタマイズやJavaScriptで色々追加や削除、イベントの追加などの操作がこれで可能になります。
- 値はformブリッジに入れられますが、入っていないものと見なされる・・・・・通常のフォーム類であれば問題なく入力して送信が可能です。
1 2 3 4 5 6 7 8 9 10 11 12 |
//webviewタグを取得する var webview = document.getElementById(webviewタグのID); //webview側からのIPC通信を受け取る webview.addEventListener('ipc-message', function(event) { switch(event.channel){ case "tomato": //event.argsで引数を取れる var argment = event.args[0]; break; } }); |
- 今回の事例ではwebview側から特に何も情報を取得しないので、使いません。
- getElementByIdだけでなく、document.querySelector('webview')でも可能です。
図:electronからform bridgeを操作出来た
関連リンク
- JavaScriptによるフォームの制御JavaScriptによるフォームの制御
- フォームブリッジのフォームをHTML出力したい方、お待たせです!
- 【2018/9/25以降の手順】フォームブリッジからkintone内のマスタを参照・引用したい
- formbridge CSSカスタマイズ
- ElectronのWebViewで遊ぶ
- How to send-retrieve information and manipulate the DOM from a webview with Electron Framework
- Web scraping with Electron
- electronのwebview内のHTMLデータを引っ張り出してみる(qiitaから未読数を取得する例)
- Javascript foreach input in form for Name and Value
- ピュアな JavaScript でフォーム(form)系要素の値を取得, 設定する方法一覧
- [Electron] webviewタグの使い方
- Electron 7.0.1でwebviewが動かない/表示されない
- CSRF保護なしでElectronのAngularJSを実行するには?
- 【Electron】nodeIntegration: falseのまま、RendererプロセスでElectronのモジュールを使用する
- ElectronのnodeIntegrationをfalseとしたときのプロセス間通信について
- Inject CSS into <webview> Chrome/Electron/HTML/CSS/JS
- kintoneメール通知の設定方法手順をまとめてみました!!