Skip to end of metadata
Go to start of metadata

You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 5 Next »

https://paper.dropbox.com/doc/07.-Tutorial-Creating-a-Kanban-Board--BtMwVwZsdKSgg1YGQWJrzuQPAg-RK27IFkhWjoSKV0ztoOvE

07. チュートリアル: かんばん ボードを作成する

<— Reactor Home J

一般的な 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>   

Now switch to the ‘Preview’ screen and click the Compile button. You should see your new BlackBox appear before your eyes:

Next, in our Kanban board we’re going to want to show a planet name, and a planet description. So change the HTML so that each <li> element contains a second line of text:

<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>        

Great. Doesn’t quite ‘pop’ though does it? Replace the CSS (contents of your <style> block) with this:

#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;

}

Recompile and take a look:

There, that’s looking a bit better. Feel free to make further CSS adjustments.

Ok, we have our base UI sorted, let’s start putting some data into it.

Accessing Planet Data

First, replace the contents inside our <body> tags with this:

<div id=Kanban></div>

We need to dynamically generate the HTML content from our Planet records, so this just provides us with a place to put it.

Now, for getting the data in. We need to:

  1. Query Reactor for the Planet records

  2. Create an ‘Unassigned’ column for records that don’t have a Trip No value

    1. Add an HTML element for each of these records to the ‘Unassigned’ column

  3. For each column provided in our parameter:

    1. Check our query response for records that belong in this column (based on the Trip No field, and add an HTML element for each of these records to the column

So, replace the contents inside our <script> tags with the following:

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

});

Quite a lot of code isn’t it? Let’s go through it bit-by-bit.

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

  });

}

First we set up our find request, accessing the following fields:

  • Planet ID

  • Planet Name

  • Planet Description

  • Trip No

  • Sort No

When a response comes back, we loop through the different records in the response. We create an object for each record, assign each object the above properties, and push the object onto an array of Planet objects.

We then sort the array of Planet objects based on the Sort No value.

Then we run the buildHtml() function.

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";

Now we take our comma separated list of columns and turn them into an array - this allows us to easily iterate through it later. We also start to generate our HTML for our Unassigned column.

Then we loop through our array of Planet records. For each, each check to see if there isn’t a Trip No set, and if not we add it to our Unassigned column by appending the HTML for each of these Planets. We also create a columnsIDArray array and pop our new Unassigned column into it as its first value. We’ll use this later to instruct the JavaScript library to treat it as a draggable box.

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;

}

First we loop through each Column and start generating the HTML for it, similar to how we did for the Unassigned column. 

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

Now we tell the JavaScript to place the variable containing our generated HTML into our <div id="Kanban"> element.

(Just a quick note on the technique we’re using to populate the HTML - this is quite primitive. There are much more elegant ways using the JQuery library to change or append to HTML elements, this technique we’re employing - of building a single string bit-by-bit - makes it clear what we’re actually doing).

Then we tell the JQuery library to instantiate the .sortable() method on our array of Column IDs (this is the IDs of our HTML elements)- although first we have to use the join() method to transform our array of ColumnIDs into a comma separated list of Column IDs - as this is the form that the method expects. This is what ‘activates’ our planet boxes - makes them draggable.

Now, ensure your JavaScript matches the JavaScript as above, recompile your BlackBox, and set the following parameters over on the Preview tab:

Fantastic!

The Planets might appear under different columns depending on whether they have Trip No values set. If there are none set, you should see them all under Unassigned.

If you’re seeing something completely different, or nothing at all, make sure you’ve entered the JavaScript exactly as above. Ensure you’ve imported all required JavaScript libraries - the order matters too. You must import the core JQuery library before importing the JQuery-UI library.

Failing that, enter debugging mode by popping the following into your FM data viewer:

And click the Reactor icon in the bottom-right of your web-viewer, and click Browser.

This will open the BlackBox in your default web browser, where you can use the Developers Tools to debug it.

Changing sort order

So now we’ve got our Kanban board pulling in our Planet records and sorting them into the right columns. We can move these Planets around - but these changes are not recorded. The next time you load the web-viewer, your changes will be lost (unless you also change the data back in FileMaker Pro).

The first thing we need is a way to intercept when a Planet has been moved. We can do this with an event handler.

An Event is something a user, or your browser, does. Handlers can be attached to particular events to capture when they happen, and the details of what’s happened.

To proceed, we need to consult the documentation.

https://api.jqueryui.com/sortable/#event-receive

https://api.jqueryui.com/sortable/#event-stop

These two events are useful to us. The recieve event fires when a Planet is moved into another list, and we need to update the Trip No and Sort No. The stop event fires when a Planet is moved within its own list, and we need to update the Sort No.

To capture an event, change this:

$( columnsIDsArray.join() ).sortable({

  connectWith: ".connectedSortable"

}).disableSelection();

To this:

$( 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 the Sort No values of all Planets in that column. If it does have a Sort 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 its Sort No verses the Sort 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:

  1. We move a planet to somewhere else in the Kanban board

  2. It writes the change back to FileMaker

  3. This triggers polling, triggering the reloading of the content

  4. 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.

 

<— Reactor Home J

  • No labels