07. チュートリアル: かんばん ボードを作成する
一般的な Reactor ガイドと FRToolbox リファレンス ドキュメントでは、Reactor が提供できる一連の機能について概説しています。ただし、FileMaker で実現できないことは実際には実現していません。
Reactor の本来の力は、ダイナミックライブラリを採用した完全にインタラクティブな JavaScript ユーザーインターフェイスで Reactor の機能を使用するときに発揮されます。
この記事では、Reactor を使用して完全にインタラクティブなかんばんボードを作成する方法について説明します。かんばんボードとは、タスクがさまざまな段階でどのように進行するかを示すために使用される視覚的なプロジェクト管理ツールです。壁に貼るポストイットでも、ソフトウェアで実現することもできます。
最初のステップは、できるだけゴールに近づけるライブラリを見つけることです。なぜ誰かがすでに書いたものを書くのですか? jQuery ライブラリにはまさに必要なものが含まれています。
Sortable
インタラクションには、グリッド内でアイテムを並べ替えることができる「Connect Lists」の例があります。垂直方向に上下に移動することも、水平方向に移動することもできます。
https://jqueryui.com/sortable/#connect-lists
では、作りたいものをまとめましょう。私たちは 3 つの宇宙展を計画しており、さまざまな惑星を訪れています (おそらく未来の超高速宇宙船で)。旅行は水平に表され、惑星をこれらの「旅行」の 1 つにドラッグし、惑星を訪れる順序を変更することもできます。
それでは、BlackBox の構築を始めましょう。
Reactor Core のメイン画面から、[+] ボタンをクリックして開始します。
私たちはゼロから始めています。
BlackBox に名前と説明を付けます。:
JQuery ライブラリを使用しているので、ソース ファイルとして次を追加します。:
https://www.dropbox.com/s/i0bw4rpuokdxa3p/jquery-3.4.1.min.js?dl=0
https://www.dropbox.com/s/jpajabb9a9iee88/jquery-ui.min.js?dl=0
[関数] タブに切り替えて、関数と次のパラメーターを追加します。:
最初の 5 つはすべて FileMaker のフィールドです。:
PlanetIDField: 惑星レコードの一意の ID
PlanetNameField: 惑星の名前を含むフィールド
PlanetDescriptionField: 惑星の説明を含むフィールド
PlanetTripNoField: これは、惑星に対して選択した「トリップ」を表します
PlanetSortNoField: これは、トリップ内の惑星の順序を表します
そして最後はテキストパラメータです:
Columns: 表示する列のカンマ区切りリスト
関数の基本を構築したので、次はテンプレートファイルを構築します。
テンプレートファイルとは?これについては、一般的な Reactor チュートリアルで詳しく読むことができますが、基本的には、始めるために最善を尽くします。
JavaScript ライブラリ (JQuery など) を追加した場合は、これが確実にインポートされます。フィールド名である 5 つのパラメーターがある場合、これらは変数として格納されます。また、これらのフィールド名に基づいてレコードを読み取り、更新、作成、または削除するメソッドを開始することにもなりますが、このための独自のメソッドを作成します。
これらの関数をすべて削除し、新しい HTML ソース ファイルが次のようになっていることを確認します。:
<html> <head> <style> <!–– CSS Goes here ––> </style> </head> <script type='text/javascript' src='FRToolBox.js'></script> <script src="jquery-3.4.1.min.js"></script> <script src="jquery-ui.min.js"></script> <!–– Import other sources here ––> <script> FMLink = 'FM.link'; // This is where variables are set from the given parameters var RecordsObj = new Object; FMTableOccurrenceName = '<?Reactor bbdev_TO( $PlanetIDField ) Reactor?>'; FunctionWhereClause = '<?Reactor bbdev_Relationship( bbdev_TO( $PlanetIDField ) ) Reactor?>'; CreationValues = '<?reactor bbdev_relationshipKeyBuilder ( $FMTableOccurrenceName ) reactor?>'; PlanetIDField = '<?Reactor bbdev_Field( $PlanetIDField ) Reactor?>'; PlanetNameField = '<?Reactor bbdev_Field( $PlanetNameField ) Reactor?>'; PlanetDescriptionField = '<?Reactor bbdev_Field( $PlanetDescriptionField ) Reactor?>'; PlanetTripNoField = '<?Reactor bbdev_Field( $PlanetTripNoField ) Reactor?>'; PlanetSortNoField = '<?Reactor bbdev_Field( $PlanetSortNoField ) Reactor?>'; Columns = '<?Reactor $Columns Reactor?>'; </script> <body> <div> <!–– This is the main HTML for the BlackBox ––> </div> </body> </html>
BlackBox の最初のステップは、UI を起動して実行することです。それが完了したら、FileMaker データをそれに接続します。最初に UI を作成する理由は、FileMaker データをどこかに置くためです。そこから始めたら、暗い無関心な空虚にデータを提供することになります。
では、その JQuery の例に戻り、 view source
をクリックしましょう。:
残念ながら、これは単純なコピー アンド ペーストではなく、3 つの単純なコピー アンド ペーストです。コピーs&ペースト?
1. <style>
タグの間のすべてのコンテンツをコピーし、BB <style>
タグの間に貼り付けます。:
#sortable1, #sortable2 { border: 1px solid #eee; width: 142px; min-height: 20px; list-style-type: none; margin: 0; padding: 5px 0 0 0; float: left; margin-right: 10px; } #sortable1 li, #sortable2 li { margin: 0 5px 5px 5px; padding: 5px; font-size: 1.2em; width: 120px; }
2. $( function()
ブロックをコピーして、BB <script>
ブロックの最後に貼り付けます。:
$( function() { $( "#sortable1, #sortable2" ).sortable({ connectWith: ".connectedSortable" }).disableSelection(); } );
3. 下の方にある HTML をコピーして、BB <body>
タグの間に貼り付けます。
<ul id="sortable1" class="connectedSortable"> <li class="ui-state-default">Item 1</li> <li class="ui-state-default">Item 2</li> <li class="ui-state-default">Item 3</li> <li class="ui-state-default">Item 4</li> <li class="ui-state-default">Item 5</li> </ul> <ul id="sortable2" class="connectedSortable"> <li class="ui-state-highlight">Item 1</li> <li class="ui-state-highlight">Item 2</li> <li class="ui-state-highlight">Item 3</li> <li class="ui-state-highlight">Item 4</li> <li class="ui-state-highlight">Item 5</li> </ul>
HTML ファイルの完全な内容は次のようになります。
<html> <head> <style> #sortable1, #sortable2 { border: 1px solid #eee; width: 142px; min-height: 20px; list-style-type: none; margin: 0; padding: 5px 0 0 0; float: left; margin-right: 10px; } #sortable1 li, #sortable2 li { margin: 0 5px 5px 5px; padding: 5px; font-size: 1.2em; width: 120px; } </style> </head> <script type='text/javascript' src='FRToolBox.js'></script> <script src="jquery-3.4.1.min.js"></script> <script src="jquery-ui.min.js"></script> <script> FMLink = 'FM.link'; FMTableOccurrenceName = '<?Reactor bbdev_TO( $PlanetIDField ) Reactor?>'; FunctionWhereClause = '<?Reactor bbdev_Relationship( bbdev_TO( $PlanetIDField ) ) Reactor?>'; CreationValues = '<?reactor bbdev_relationshipKeyBuilder ( $FMTableOccurrenceName ) reactor?>'; PlanetIDField = '<?Reactor bbdev_Field( $PlanetIDField ) Reactor?>'; PlanetNameField = '<?Reactor bbdev_Field( $PlanetNameField ) Reactor?>'; PlanetDescriptionField = '<?Reactor bbdev_Field( $PlanetDescriptionField ) Reactor?>'; PlanetTripNoField = '<?Reactor bbdev_Field( $PlanetTripNoField ) Reactor?>'; PlanetSortNoField = '<?Reactor bbdev_Field( $PlanetSortNoField ) Reactor?>'; Columns = '<?Reactor $Columns Reactor?>'; $( function() { $( "#sortable1, #sortable2" ).sortable({ connectWith: ".connectedSortable" }).disableSelection(); } ); </script> <body> <ul id="sortable1" class="connectedSortable"> <li class="ui-state-default">Item 1</li> <li class="ui-state-default">Item 2</li> <li class="ui-state-default">Item 3</li> <li class="ui-state-default">Item 4</li> <li class="ui-state-default">Item 5</li> </ul> <ul id="sortable2" class="connectedSortable"> <li class="ui-state-highlight">Item 1</li> <li class="ui-state-highlight">Item 2</li> <li class="ui-state-highlight">Item 3</li> <li class="ui-state-highlight">Item 4</li> <li class="ui-state-highlight">Item 5</li> </ul> </body> </html>
「プレビュー」画面に切り替えて、「コンパイル」ボタンをクリックします。新しい BlackBox が目の前に表示されるはずです。:
次に、かんばんボードに惑星名と惑星の説明を表示します。したがって、各 <li>
要素に 2 行目のテキストが含まれるように HTML を変更します。:
<ul id="sortable1" class="connectedSortable"> <li><div class=name>Item 1</div><div class=desc>The first item</div></li> <li><div class=name>Item 2</div><div class=desc>The second item</div></li> <li><div class=name>Item 3</div><div class=desc>The third item</div></li> <li><div class=name>Item 4</div><div class=desc>The fourth item</div></li> <li><div class=name>Item 5</div><div class=desc>The fifth item</div></li> </ul> <ul id="sortable2" class="connectedSortable"> <li><div class=name>Item 1</div><div class=desc>The first item</div></li> <li><div class=name>Item 2</div><div class=desc>The second item</div></li> <li><div class=name>Item 3</div><div class=desc>The third item</div></li> <li><div class=name>Item 4</div><div class=desc>The fourth item</div></li> <li><div class=name>Item 5</div><div class=desc>The fifth item</div></li> </ul>
いいですね。しかし、それはかなり「ポップ」ではありませんか? CSS ( <style>
ブロックの内容) を次のように置き換えます。:
#sortable1, #sortable2 { width: 142px; min-height: 20px; list-style-type: none; margin: 0; padding: 5px 0 0 0; float: left; margin-right: 10px; } #sortable1 li, #sortable2 li { margin: 0 5px 5px 5px; padding: 5px; width: 120px; border:black solid 1px; background-color:#123a53; color:white; font-size:16px; }
再コンパイルして見てみましょう:
ほら、少し良くなっています。さらにCSSを自由に調整してください。
OK、ベース UI がソートされたので、いくつかのデータを入力してみましょう。
惑星データへのアクセス
まず、 <body>
タグ内のコンテンツを次のように置き換えます:
<div id=Kanban></div>
Planet
レコードから HTML コンテンツを動的に生成する必要があるため、これは単にそれを配置する場所を提供するだけです。
次に、データを取得します。次のことを行う必要があります。:
Reactor で Planetレコードへのクエリを行う
Trip No
の値を持たないレコードの「未割り当て」列を作成しますこれらの各レコードの HTML 要素を [未割り当て] 列に追加します
パラメータで指定された各列について:
この列に属するレコードのクエリ応答を確認します。 (
Trip No
フィールドに基づいて、これらの各レコードの HTML 要素を列に追加します)
したがって、 <script>
タグ内のコンテンツを次のように置き換えます。:
FMLink = 'FM.link'; // This is where variables are set from the given parameters FMTableOccurrenceName = '<?Reactor bbdev_TO( $PlanetIDField ) Reactor?>'; FunctionWhereClause = '<?Reactor bbdev_Relationship( bbdev_TO( $PlanetIDField ) ) Reactor?>'; CreationValues = '<?reactor bbdev_relationshipKeyBuilder ( $FMTableOccurrenceName ) reactor?>'; PlanetIDField = '<?Reactor bbdev_Field( $PlanetIDField ) Reactor?>'; PlanetNameField = '<?Reactor bbdev_Field( $PlanetNameField ) Reactor?>'; PlanetDescriptionField = '<?Reactor bbdev_Field( $PlanetDescriptionField ) Reactor?>'; PlanetTripNoField = '<?Reactor bbdev_Field( $PlanetTripNoField ) Reactor?>'; PlanetSortNoField = '<?Reactor bbdev_Field( $PlanetSortNoField ) Reactor?>'; Columns = '<?Reactor $Columns Reactor?>'; var PlanetsArr = new Array(); // Populate columns function findPlanets() { FRTB.find( FMTableOccurrenceName + "::" + PlanetIDField, FMTableOccurrenceName + "::" + PlanetNameField, FMTableOccurrenceName + "::" + PlanetDescriptionField, FMTableOccurrenceName + "::" + PlanetTripNoField, FMTableOccurrenceName + "::" + PlanetSortNoField ).where(FunctionWhereClause).send(function(response) { for ( var i=0; i < response.data.length; i++) { ThisPlanetObj = new Object(); ThisPlanetObj.id = response.data[i][FMTableOccurrenceName + "::" + PlanetIDField]; ThisPlanetObj.name = response.data[i][FMTableOccurrenceName + "::" + PlanetNameField]; ThisPlanetObj.description = response.data[i][FMTableOccurrenceName + "::" + PlanetDescriptionField]; ThisPlanetObj.trip_no = response.data[i][FMTableOccurrenceName + "::" + PlanetTripNoField]; ThisPlanetObj.sort_no = response.data[i][FMTableOccurrenceName + "::" + PlanetSortNoField]; PlanetsArr.push(ThisPlanetObj); } buildHtml(); PlanetsArr.sort(function(a, b){ return a.sort_no > b.sort_no; }); }); } function buildHtml() { ColumnsArr = Columns.split(","); htmlString = ""; columnsIDsArray = Array(); htmlString += "<ul id='sortable_unassigned' class='connectedSortable'>"; htmlString += "<div class=Column>Unassigned</div>"; for ( var i=0; i < PlanetsArr.length; i++) { if (PlanetsArr[i].trip_no == "") { htmlString += "<li class=name id='" + PlanetsArr[i].id + "'><div>"; htmlString += PlanetsArr[i].name; htmlString += "</div><div class=desc>"; htmlString += PlanetsArr[i].description; htmlString += "</div></li>"; } } htmlString += "</ul>"; columnsIDsArray[0] = "#sortable_unassigned"; for ( var i=0; i < ColumnsArr.length; i++) { htmlString += "<ul id='sortable_" + i + "' class='connectedSortable'>"; htmlString += "<div class=Column>" + ColumnsArr[i] + "</div>"; for ( var j=0; j < PlanetsArr.length; j++) { if (PlanetsArr[j].trip_no == (i+1)) { htmlString += "<li class=name id='" + PlanetsArr[j].id + "'><div>"; htmlString += PlanetsArr[j].name; htmlString += "</div><div class=desc>"; htmlString += PlanetsArr[j].description; htmlString += "</div></li>"; } } htmlString += "</ul>"; columnsIDsArray[i+1] = "#sortable_" + i; } $('#Kanban').html(htmlString); $( columnsIDsArray.join() ).sortable({ connectWith: ".connectedSortable" }).disableSelection(); } $( function() { findPlanets(); });
かなりたくさんのコードですね。少しずつ見ていきましょう。
惑星データのクエリ Query Planet Data
function findPlanets() { FRTB.find( FMTableOccurrenceName + "::" + PlanetIDField, FMTableOccurrenceName + "::" + PlanetNameField, FMTableOccurrenceName + "::" + PlanetDescriptionField, FMTableOccurrenceName + "::" + PlanetTripNoField, FMTableOccurrenceName + "::" + PlanetSortNoField ).where(FunctionWhereClause).send(function(response) { $('#Kanban').html(""); PlanetsArr = new Array(); for ( var i=0; i < response.data.length; i++) { ThisPlanetObj = new Object(); ThisPlanetObj.id = response.data[i][FMTableOccurrenceName + "::" + PlanetIDField]; ThisPlanetObj.name = response.data[i][FMTableOccurrenceName + "::" + PlanetNameField]; ThisPlanetObj.description = response.data[i][FMTableOccurrenceName + "::" + PlanetDescriptionField]; ThisPlanetObj.trip_no = response.data[i][FMTableOccurrenceName + "::" + PlanetTripNoField]; ThisPlanetObj.sort_no = response.data[i][FMTableOccurrenceName + "::" + PlanetSortNoField]; PlanetsArr.push(ThisPlanetObj); } PlanetsArr.sort(function(a, b){ return a.sort_no > b.sort_no; }); buildHtml(); }); }
まず、次のフィールドにアクセスして検索条件を設定します。:
Planet ID (惑星ID)
Planet Name (惑星名)
Planet Description (惑星の説明)
Trip No (旅行番号)
Sort No (ソート番号)
応答が返ってきたら、応答内のさまざまなレコードをループします。レコードごとにオブジェクトを作成し、各オブジェクトに上記のプロパティを割り当て、そのオブジェクトを Planet オブジェクトの配列にプッシュします。
次に、Sort No
の値に基づいて Planet オブジェクトの配列を並べ替えます。
次に、buildHtml()
関数を実行します。
Trip Noのないレコードの列を追加し、それに惑星を割り当てる Add column for records without a Trip No and assign planets to it
ColumnsArr = Columns.split(","); htmlString = ""; columnsIDsArray = Array(); htmlString += "<ul id='sortable_unassigned' class='connectedSortable'>"; htmlString += "<div class=Column>Unassigned</div>"; for ( var i=0; i < PlanetsArr.length; i++) { if (PlanetsArr[i].trip_no == "") { htmlString += "<li class=name id='" + PlanetsArr[i].id + "'><div>"; htmlString += PlanetsArr[i].name; htmlString += "</div><div class=desc>"; htmlString += PlanetsArr[i].description; htmlString += "</div></li>"; } } htmlString += "</ul>"; columnsIDsArray[0] = "#sortable_unassigned";
次に、コンマ区切りの列のリストを取得して配列に変換します。これにより、後で簡単に反復処理を行うことができます。Unassigned
列の HTML の生成も開始します。
次に、Planet レコードの配列をループします。それぞれについて、Trip No
セットがないかどうかを確認し、そうでない場合は、これらの惑星のそれぞれに HTML を追加することによって、それを Unassigned
列に追加します。また、columnsIDArray
配列を作成し、新しい Unassigned
列を最初の値としてそこに取り込みます。後でこれを使用して、ドラッグ可能なボックスとして扱うように JavaScript ライブラリに指示します。
各列に目を通し、Planet レコードを分類します Go through each column, and categorise our Planet records
for ( var i=0; i < ColumnsArr.length; i++) { htmlString += "<ul id='sortable_" + i + "' class='connectedSortable'>"; htmlString += "<div class=Column>" + ColumnsArr[i] + "</div>"; for ( var j=0; j < PlanetsArr.length; j++) { if (PlanetsArr[j].trip_no == (i+1)) { htmlString += "<li class=name id='" + PlanetsArr[j].id + "'><div>"; htmlString += PlanetsArr[j].name; htmlString += "</div><div class=desc>"; htmlString += PlanetsArr[j].description; htmlString += "</div></li>"; } } htmlString += "</ul>"; columnsIDsArray[i+1] = "#sortable_" + i; }
最初に各Column
をループし、Unassigned
列に対して行った方法と同様に、その列の HTML の生成を開始します。
次に、Column
ごとに Planet レコードの配列をループし、Planet レコードがこの Column に属している場合は、これらの惑星ごとに HTML を追加して、その列に追加します。次の列にループする前に、columnsIDArray
に挿入します。
Then for each Column
we loop through our array of Planet records, and if a Planet record belongs in this Column
we add it to that column by appending the HTML for each of these planets. Before we loop to the next Column
we pop it onto our columnsIDArray
.
ドラッグ可能なリストをインスタンス化する Instantiate our draggable list
$('#Kanban').html(htmlString); $( columnsIDsArray.join() ).sortable({ connectWith: ".connectedSortable" }).disableSelection();
次に、生成された HTML を含む変数を <div id="Kanban">
要素に配置するよう JavaScript に指示します。
(HTML に入力するために使用している手法について簡単に説明します。これは非常に原始的です。JQuery ライブラリを使用して、HTML 要素を変更または追加するためのはるかに洗練された方法がありますが、この手法を採用しています。単一の文字列ビットごと - 実際に何をしているかを明確にします)。
次に、列 ID の配列 (これは HTML 要素の ID) で .sortable()
メソッドをインスタンス化するように JQuery ライブラリに指示しますが、最初に join()
メソッドを使用して列 ID の配列を列 ID のコンマ区切りリスト - これはメソッドが期待する形式です。これがプラネット ボックスを「アクティブ化」し、ドラッグ可能にします。
ここで、JavaScript が上記の JavaScript と一致することを確認し、BlackBox を再コンパイルして、[プレビュー] タブで次のパラメーターを設定します。:
素晴らしい!
惑星は、Trip No
値が設定されているかどうかに応じて、異なる列の下に表示される場合があります。何も設定されていない場合は、未割り当ての下にすべて表示されます。
まったく異なるもの、またはまったく何も表示されない場合は、上記のように JavaScript を正確に入力したことを確認してください。必要なすべての JavaScript ライブラリをインポートしたことを確認してください。順序も重要です。 JQuery-UI ライブラリをインポートする前に、コア JQuery ライブラリをインポートする必要があります。
それができない場合は、FM データビューアに次のコードを入力してデバッグ モードに入ります。:
Web ビューアの右下にある Reactor アイコンをクリックし、Browser
をクリックします。
これにより、デフォルトの Web ブラウザで BlackBox が開き、開発者ツールを使用してデバッグできます。
ソート順の変更 Changing sort order
これで、かんばんボードに Planet レコードを取り込み、正しい列に並べ替えることができました。これらの惑星を移動することはできますが、これらの変更は記録されません。次に Webビューアを読み込むと、変更は失われます (FileMaker Pro でデータを元に戻さない限り)
最初に必要なのは、Planet が移動したときにインターセプトする方法です。これは、イベントハンドラーを使用して実行できます。
イベントとは、ユーザまたはブラウザが行うことです。ハンドラーを特定のイベントにアタッチして、イベントがいつ発生したか、および何が起こったかの詳細をキャプチャできます。
続行するには、ドキュメントを参照する必要があります。
https://api.jqueryui.com/sortable/#event-receive
https://api.jqueryui.com/sortable/#event-stop
この 2 つのイベントは、私たちにとって有益です。惑星が別のリストに移動すると、recieve
受信イベントが発生し、Trip No
とSort No
を更新する必要があります。惑星が独自のリスト内で移動すると、stop
停止イベントが発生し、Sort No
を更新する必要があります。
イベントをキャプチャするには、次のように変更します。:
$( columnsIDsArray.join() ).sortable({ connectWith: ".connectedSortable" }).disableSelection();
これに:
$( columnsIDsArray.join() ).sortable({ connectWith: ".connectedSortable", receive: function (event,ui) { // Do something } }).disableSelection();
So let’s have a think about what we need to tell FileMaker when dragging a Planet to a different location. First off, we’re changing what column it’s assigned to, so we need to write that back. But just as importantly, we have effectively changed the sort order in the target column, as the Planets that sit below where we’re dragging to are now pushed down the sort order.
So we need to write the sort order of all records in the target column, including the record we’ve just moved.
To do this, we need to know the contents of our target column. We can do this by looking at the child elements that belong to the column element:
$("#" + event.target.id)[0].childNodes[3].id;
This would return the record ID of the third planet below the target. Not sure how this works?
When we generated our HTML after querying our Planet records, we stored the ID against the element for the Planet:
htmlString += "<li class=name id='" + PlanetsArr[j].id + "'><div>";
So we look at the HTML element of our column, look at the child elements, and inspect each for our PlanetID.
So now we just need to loop through each of these IDs, and update the sort order. We can start at 1, and increase it by 1 every time.
So replace our .sortable()
method call with this:
$( columnsIDsArray.join() ).sortable({
connectWith: ".connectedSortable",
stop: saveLocation,
receive: saveLocation,
}).disableSelection();
This will call the same function for both stop
and receive
events.
Write the function somewhere in our JavaScript prior to this - the best place would be before the findPlanets()
function.
function saveLocation(event,ui) {
NewTripNo = PlanetID = $("#" + event.target.id)[0].childNodes[0].innerText.replace("Trip ","");
for (var i=1; i <= $("#" + event.target.id)[0].childNodes.length; i++) {
PlanetID = $("#" + event.target.id)[0].childNodes[i].id;
FRTB.update(
[FMTableOccurrenceName + "::" + PlanetSortNoField, i],
[FMTableOccurrenceName + "::" + PlanetTripNoField, NewTripNo]
).filter(
FMTableOccurrenceName + "::" + PlanetIDField + "=" + PlanetID
).send();
}
}
Now for our obligatory step through what it does. First we the extract trip number of the number we’ve moved our Planet to - to do this we pull out the text name of the Column HTML element (the column text that actually displays), and parse out the actual trip number. If it’s our Unassigned
column we ensure that a blank value will be set.
Then we loop through every Planet within this column, grab its ID, and update the sort number, and trip number, for each record based on each ID.
For the sort number, we just send through the current “for loop” value, this will ensure the order they appear under the column will be the relative order numbers for each record.
So if you recompile, switch to the preview tab, any changes you make should be persistent. Move a planet to a different space in the list, and it will remember the new order. Move a planet to a different column, it should remember that it’s now under the column, and its order within that column - you should be able to switch to a different tab, then switch back, and it will remain the same.
If your BlackBox doesn’t remember any of this, ensure your function is exactly as written above, and you set it against the events as in the snippet above that.
We could stop right here. It brings our Planets from FileMaker into our BlackBox, and allows us to move them around and have the changes write back to FileMaker. So next time it loads it will remember where you left everything.
However, let’s ponder for a moment - let’s say we’re building this for a database hosted on FileMaker Server. We’re using the Kanban board to plan our various exhibitions, and another one of our Space Trip Planners moves Venus into Trip 2. If we want your Kanban board to update in real-time we’ll need to employ polling.
Polling for changes
Now we’re ensuring that our interactions with our BlackBox are persistent, it’s not just for show - it’s actually doing something.
What if one day we discover a new planet? What if something terrible happens and one of our planets disappears? What if the name/description of a planet changes? Then our BlackBox would be showing out-of-date information.
To pick up on record changes, apply the .poll()
method to our original query that brings in all the Planet data, and replace this line:
).where(FunctionWhereClause).send(function(response) {
With this:
).where(FunctionWhereClause).poll(pollChanges).send(function(response) {
This instructs Reactor so that once the query is complete, it will continue to poll for changes (default is every 5 seconds).
When a new planet is discovered we need to populate it into the relevant column. But where in the column do we put it? The Sort No
could have been set by another user, which could affect the position of all other Planets in that column. So to play it safe, we need to re-query all Planets that belong to that column, and re-list them based on their order.
When an existing planet gets a new name/description, we we need to show those new details. But what if the Trip No
changes? Then we need to update the original column to remove it, and update the target column to add it. What if the Sort No
changes, then similar to new planets, we need to re-list both columns.
What if an existing planet disappears, and is deleted? We’d need to update the column to remove it.
We could really tailor these polling scenarios to update the relevant UI elements. For example:
New Planet
Add it to the relevant column. If it has no
Sort No
set, put it at the bottom of the column, and write back theSort No
values of all Planets in that column. If it does have aSort No
set, we’d need to place it in the appropriate place in the column.
Updated Planet
When an existing planet has details changed, it’s possible that the
Sort No
could have changed, so to ensure the order of the Planets in that column are correct we’d need to check itsSort No
verses theSort No
of the planets above/below it. If it should be above/below an adjacent Planet and it’s not, then they should be switched, and then check the Planet above/below it again - and keep doing this comparison until its reached its correct location.
Deleted Planet
This one’s much simpler. If a planet disappears, we simply need to remove it from its relevant column
Where did we lose you? We assume we lost you somewhere in that confusion.
If we were to enforce all these interactions in our polling, this guide would get insufferably long - and frankly it’s not necessary. Instead, we’ll simply redraw the Kanban board any time Reactor reports that there are new records, updated records, or deleted records, from our original found set.
So write a polling function as so:
function redrawBoard(results) {
if (OverridePoll) return;
if (results.remove.length > 0 || results.create.length > 0 || results.update.length > 0) {
findPlanets();
}
}
And tell our initial query to run this function when polling detects a new/changed/removed record:
).where(FunctionWhereClause).poll(redrawBoard).send(function(response) {
So what does our function do? It’s quite simple. It automatically gets passed through the results of the polling. This is an object containing three arrays of row ID’s. One array for new records, one array for changed records, and one array for deleted records. What is a row ID? It’s a number that FileMaker uses internally to identify records, any Reactor query automatically includes the row ID in its response, regardless of whether or not you asked for it.
However in this example we don’t need to know what records have been changed/added/deleted, all we need to know is that there are records that have been changed/added/deleted. So simply checking the lengths of the three arrays to see if any have values will do, for our purposes.
If there are records changed/added/deleted we simply run our findPlanets()
function, this re-queries the data and displays it - the same as when the BlackBox first loads.
However it’s not ready yet - we need to ignore any polling changes when we’re the ones writing the data back. Why? Think about this sequence of events:
We move a planet to somewhere else in the Kanban board
It writes the change back to FileMaker
This triggers polling, triggering the reloading of the content
The content reloads in the middle of the ‘dragging’ action, halting the action
This is why we check the OverridePoll
variable at the start of the function.
In our saveLocation()
function, we need to set the OverridePoll
variable to true
:
function saveLocation(event,ui) {
var OverridePoll = true;
...}
And set it to false when the FRTB.update()
function is completed:
.send(function(response) {
var OverridePoll = false;
});
And since the variable is used in multiple functions we need to define it globally. So if you add it to our global variables before our functions, any function will have access to it:
...
Columns = '<?Reactor $Columns Reactor?>';
var PlanetsArr = new Array();
var OverridePoll = false;
There, now we have an option to Override polling. This means while this Override is activated, any polling will be ignored, so we activate it at the start of our action that writes to FileMaker, and we deactivate it when it has been written.
So now if you pop into the data layout for Planets, you’ll be able to edit the raw data and should see the Kanban board changing before your eyes.
You’ve successfully taken a dynamic JavaScript library, and implemented it using Reactor to communicate with FileMaker. Any visual interface you wish to create in Reactor involves these core principles:
Read data into your BlackBox
Intercept events that should change data, and write changes back to FileMaker (adding a record, updating a record, editing record fields, or even running a script - though that isn’t part of this guide)
Show data changes in real time
Any BlackBox comes down to these things. Some only read data, same also write data back, others also use polling.
Were we being a bit presumptuous in arrogantly declaring your BlackBox is working? Luckily we prepared one earlier. Check out our Kanban BlackBox that comes included in our Reactor Core, or just download our completed BlackBox file and see where you went wrong:
https://www.dropbox.com/s/lz3p751qezikjsu/PlanetKanban.rbb?dl=0
Import it into your Reactor, by clicking ‘Import a new BlackBox’ when creating a new BlackBox. Just be sure to rename the BlackBox and function if they’re the same name as yours - otherwise Moogie will get confused.