Skip to main content

クライアントサイドでのJavascriptを使用したビジネスロジック

このガイドで学べること

このガイドでは、アプリのクライアント(フロントエンド)側でビジネスロジックをJavascriptを使用して実装する方法を示します。ロジックノードを使用してノーコードでビジネスロジックを構築することも可能ですが(このガイドで説明されています)、ビジネスロジックはコード形式で表現した方が理解しやすいことが多いため、Javascriptの使用がしばしば推奨されます。このガイドでは、FunctionおよびScriptノードを使用して、Noodlでコードとノーコードを組み合わせる主な方法を取り上げます。 また、コード世界とノード世界の両方で状態データに簡単にアクセスできるようにする主な方法であるComponent Objectノードも重要です。

概要

このガイドでは、リスト上でのシンプルなマルチセレクトの相互作用と、削除やコピーなどのいくつかのマルチセレクト操作を実装します。ビジネスロジックはマルチセレクト自体(選択、選択解除)およびリストの内容に対する操作を扱います。また、画面上のButtonsの状態制御も行います。

リスト、配列、オブジェクトなど、さまざまなNoodlのコンセプトを使用しますので、これらのガイドを先に確認しておくと良いでしょう(List guide, Array guide, Object guide)。もちろん、Javascriptも使用しますし、特に配列関数を多用するので、それらについてもチェックしておく価値があります。良いドキュメントの例としては、こちらがあります。

Scriptノードに関する専用ガイドもあります。こちらを参照してください。

マルチセレクトUIを持つリストの作成

この例では、例えばeコマースシステムでの、order_nbrquantitydelivery_dateを持つ注文のシンプルなコレクションを扱います。アプリは注文をリスト表示し、ユーザーが標準的なマルチセレクトの相互作用を使用して複数の注文を選択できるようにします。選択した注文は、削除コピー、または_マージ_することができます。実際の操作がどういう意味を持つのかは、実装していく中で詳しく見ていきます。また、全ての項目を_選択_または_選択解除_することも可能です。

基本データ

このガイドでは、作業したい基本データセットを追加して始めます。データを保持するためにStatic Arrayを使用しますが、本質的にこのデータはクラウドデータベースから来たものかもしれません。"Hello World"テンプレートを使用して新しいプロジェクトを開始します。Static Arrayノードを追加し、JSONフォーマットに設定し、以下のデータを追加します:

[   
{"order_nbr":"A-12124", "quantity":2, "delivery_date":"2022-10-23"},
{"order_nbr":"A-26232", "quantity":6, "delivery_date":"2022-10-25"},
{"order_nbr":"V-23532", "quantity":3, "delivery_date":"2022-09-13"},
{"order_nbr":"B-99243", "quantity":5, "delivery_date":"2022-08-03"},
{"order_nbr":"V-35124", "quantity":1, "delivery_date":"2022-12-20"},
{"order_nbr":"G-23421", "quantity":1, "delivery_date":"2022-09-09"},
{"order_nbr":"B-86612", "quantity":8, "delivery_date":"2022-11-21"},
{"order_nbr":"C-61633", "quantity":5, "delivery_date":"2022-05-29"},
{"order_nbr":"V-42241", "quantity":2, "delivery_date":"2022-11-15"},
{"order_nbr":"V-99112", "quantity":12, "delivery_date":"2022-12-20"},
{"order_nbr":"A-51512", "quantity":1, "delivery_date":"2022-07-07"},
{"order_nbr":"B-00914", "quantity":8, "delivery_date":"2022-09-13"},
{"order_nbr":"C-11121", "quantity":9, "delivery_date":"2022-10-19"}
]

これでシステムにいくつかの注文が入りましたので、UIの構築を

始めましょう。マルチセレクトリストから始めます。

マルチセレクトリストの構築

リストに表示されるアイテムの配列を示すコンポーネントを構築したいと思います。また、選択されているアイテムを追跡する機能も必要です。以下にその機能を概説します。

  • アイテムがリストに表示されるようにする機能。アイテムの前にはチェックボックスが表示されます。
  • 初期選択を指定できる機能、つまり、どのアイテムが最初に選択されるべきかを指定できます。
  • アイテムを選択または選択解除すると、コンポーネントはシグナルを発生させ、現在選択されているアイテムのArrayを提供します。
  • すべてのアイテムを選択または選択解除するために、シグナルをトリガーできます。

マルチセレクトリストがどのアイテムが選択されているかをどのようにエンコードするかという問題があります。一つの方法は、マルチセレクトリストが選択されているアイテムのArrayを以下のフォーマットで提供することです:

{
"Value":<選択されたオブジェクトのid>
}

例えば、リストに"111"、"222"、"333"のidを持つ3つのアイテムがあり、最初と2番目のアイテムが選択されている場合、コンポーネントからの出力は以下のようになります:

[
{
"Value":"111"
},
{
"Value":"222"
}
]

完了すると、外側から見たときのコンポーネントは以下のようになります:

選択ロジック

コンポーネントに必要なロジックを考えてみましょう。内部的には、どのアイテムが選択されていて、どれが選択されていないかを追跡する必要があります。これは、上記のArrayを生成するためだけでなく、正しく視覚化するためにも必要です。

もちろん、選択状態をアイテム自体に直接保存することもできますが、それは実際にはあまり良い設計ではありません。アイテムが複数のマルチセレクトリストの一部である場合はどうなるでしょうか?また、アイテムをデータベースに保存することになった場合、選択状態も誤って保存する可能性がありますが、それは全く意味がありません。

より良い設計は、選択状態を追跡するために各注文を別のObjectで"ラップ"することです。その後、元のアイテムではなく、これらのオブジェクトのリストを表示します。"ラッパー"は当然、それがラップするObjectIdを保存する必要があります。ラッパーのフォーマットは以下のようになります:

[
{
"Checked":<アイテムがチェックされているかどうかに応じてtrueまたはfalse>,
"Value":<それがラップするObjectのid>
}
]

したがって、上記の例でid "111"、"222"、"333"を持つアイテムがあり、最初の2つがチェックされている場合、チェックボックスの配列は以下のようになります:

[
{
"Checked":true,
"Value":"111"
},
{
"Checked":true,
"Value":"222"
},
{
"Checked":false,
"Value":"333"
},
]

要約すると、マルチセレクトリストコンポーネントには内部で以下のロジックが必要です

  • 新しいアイテムでリストが供給されると、アイテムごとに1つの新しいチェックボックスArray(ラッパーObject)を作成し、初期選択に応じてCheckedtrueまたはfalseに設定します。
  • 各アイテムを表示するリストアイテムは、Checkedプロパティを使用してアイテムのチェック状態を視覚化します。ユーザーがチェックボックスをクリックすると、Checkedプロパティがトグルされます。
  • チェック状態が変更されるたびに、マルチセレクトリストはチェックされたアイテムのみを保持する新しいリストを生成する必要があります。この形式は、上記のように、[{"Value":<チェックされたアイテムのid>}, ...]です。これは外部世界に提示されます。

"Multi Select List"という名前の新しいビジュアルコンポーネントを作成

して始めましょう。

これで、ほぼ空のコンポーネントができました。Component InputsComponent Outputsを追加します。これらに何を含めるべきかは既にわかっています。つまり、コンポーネントとのやり取り方法です。したがって、Component InputsSelectionItemsSelect AllClear Selectionのポートを追加します。

Component Outputsでは、SelectionSelection Changedのポートを作成します。

これで、Javascriptロジックの記述を始める準備が整いました。Scriptノードを追加します。

2つの入力を追加します。1つはリストに表示するItems、もう1つはリストに最初に選択されるべきアイテムを含むInitialSelectionです。どちらもArrayタイプです。

さあ、コーディングを始めましょう!最初に行いたいのは、チェックボックスリスト、つまり各アイテムのチェック状態を保持するラッピングObjectsArrayを生成することです。

セッター関数 - 入力が変更されたときにコードを実行する

Items入力が変更されるたびにそのArrayを生成したい場合、_セッター_関数を使用して特定の入力が変更されるたびにスクリプトの一部を実行できます。これらは以下の形式で宣言されます:

Script.Setters.<プロパティ名> = function (value) {...}

この場合、Itemsが変更されるたびにチェックボックスArrayを生成したいので、スクリプトファイルに以下のコードを追加します:

Script.Setters.Items = function (items) {
if(!Script.Inputs.Items) return
// 入力された**Items**を走査し、それぞれのアイテムに対して新しいラッパー**Object**を作成します(`Noodl.Object.create`を使用)。
// チェック状態を確認し、初期選択配列に対して`true`または`false`を設定します。
// また、ラップされた**Object**へのポインタを`Value`プロパティに保存します。
}

このコードは、入力されたItemsScript.Inputs.Itemsを通じて参照される)を通過し、それぞれに対して新しいラッパーObjectNoodl.Object.createを使用)を作成します。チェック状態を確認し、初期選択配列に基づいてtrueまたはfalseに設定します。また、ラップされたObjectへのポインタをValueプロパティに保存します。

undefined入力のチェック

スクリプトノードの入力がまだ設定されていない場合(この場合のItems入力やInitialSelectionなど)、それらはundefinedの値を持ちます。コード内でこのケースを明示的にチェックすることは、しばしば良いアイデアです。

ここでの小さなトリックは、ラッパーObjectidを明示的に設定していることです。実際には、idが指定されていない場合、Noodlは新しいidを生成します。しかし、独自のidを作成することで、特定のオブジェクトをラップするラッパーオブジェクトごとに一意だが同じになるため、古いObjectsを再利用するのではなく新しいものを作成します。

結果として得られるArrayComponent.Object.Checkboxesに保存されます。これには特に注意が必要です。

スクリプト内でのコンポーネントオブジェクトの使用

Noodlの各コンポーネントには、一意のComponent Objectがあります。通常のObjectとは対照的に、Component Objectは_コンポーネント自体またはコンポーネントの子を使用してのみアクセスできます_。これにより、スコープが作成され、他のコンポーネントが誤ってこのオブジェクトにアクセスするリスクがありません。例えば、同じ画面に2つのマルチセレクトリストを表示する場合、通常のObjectを使用してチェックボックスを保存すると、2つのリストでチェックボックスが異なる可能性があるため、Objectsに一意のidを持たせる必要があります。この問題を完全に回避するために、

Component Objectにチェックボックスを保存します。

すべてのコンポーネントにはComponent Objectがあり、ノードワールドでComponent Objectノードを追加することでアクセスできます。したがって、一旦Scriptノードを閉じて、Component Objectノードを追加しましょう。また、Component ObjectCheckboxesプロパティを追加します。

これまでに行ったことをテストするために、Arrayノードを追加し、Component ObjectCheckboxesプロパティをそれに接続します(Itemsプロパティを介して)。次に、Multi Select Listコンポーネントをメインアプリに追加します。Static Arrayのアイテムを接続してアイテムを提供します。すべてが正しく設定されていれば、いくつかのチェックボックスアイテムがArrayに流れ込むはずです。

リストアイテムの作成

これで、リストアイテムを作成する準備が整いました。Multi Select Listコンポーネントをフォルダにまとめましょう。新しいフォルダを作成し、Multi Select Listと名付けます。Multi Select Listコンポーネントを移動させます。次に、新しいビジュアルコンポーネントを追加します。List Itemと名付けましょう。

リストアイテムを視覚的にシンプルに保ちます。水平レイアウトでCheckboxと3つのテキストを追加します。少し美しくするためにパディングとマージンを追加します。必要であれば、ノードをコピーしてペーストしてコンポーネントに追加することができます。

データを接続しましょう。今回は2つのObjectsを考慮する必要があります。Repeaterによって提供される"ラッパー"Objectと、実際の注文です。以下のように接続します。また、Component Outputを追加して、チェックボックスがクリックされたことを知ることができます。

Multi Select ListコンポーネントにRepeaterノードを追加し、新しく作成したリストアイテムをアイテムテンプレートとして選択します。最後に、Component ObjectCheckboxes出力をリピーターのItems入力に接続します。これでリストができました!

スクリプトでシグナルをトリガーする

現在チェックされているすべてのアイテムを含む選択リスト、つまりArrayを生成するスクリプトを書く時が来ました。覚えているように、この形式で決めました:

{
"Value":<選択されたオブジェクトのid>
}

マルチセレクトロジックスクリプトノードに戻って、新しい関数を追加します。updateSelectionと名付けます。以下のようになります:

function updateSelection () {
Component.Object.Selection = Component.Object.Checkboxes.filter(o => o.Checked)
.map(o => Noodl.Object.create({
id:Component.Object.id+"_"+o.Value,
Value:o.Value}
));
}

再び、Component Objectを使用してArrayを保存しています。ArrayはチェックボックスArrayを通過し、チェックされたアイテムを探し、チェックされたアイテムのidValueに保存する新しいObject(またはidが既に存在する場合は古いものを再利用)を作成することで生成されます。

リストで使用される配列出力

note

古いアイテムを含むArrayを再利用し、それを変更することもできますが、これはRepeaterノード(またはその他のノード)によって自動的に更新をトリガーすることはありません。技術的にはArrayは変更されませんでした - 内容だけが変更されました。そのため、実際に新しいArrayを作成し、それを出力に割り当てることが重要です。それ以外の場合は、Repeatersなどを手動でトリガーして更新する必要があります。一般に、JavascriptのArray関数(filter、map、findなど)は新しいArraysを返すので、これらのケースでは非常に使いやすいです。

Itemsのセッター関数で、Checkboxes Arrayを作成した直後にupdateSelectionを呼び出して、選択Arrayがすぐに設定されるようにする必要があります。したがって、以下のように更新します:

Script.Setters.Items = function (items) {
if(!Script.Inputs.Items) return
// 初期選択配列に基づいてチェックされたアイテムのリストを作成します
Component.Object.Checkboxes = Script.Inputs.Items.map(o => Noodl.Object.create({
id:Component.Object.id+'-'+o.id,
Value:o.id,
Checked:Script.Inputs.InitialSelection!==undefined && !!Script.Inputs.InitialSelection.find(s => s.Value === o.id)
}))

updateSelection ();
}
``

`

ユーザーがチェックボックスをクリックするたびに、`updateSelection`をトリガーしてリストを再生成する必要があります。スクリプトに__シグナル__を追加するには、`Script.Signals.<シグナル名>`と書きます。これらのシグナルは**Script**ノードの入力となり、トリガーすることができます。したがって、私たちのシグナルは`UpdateSelection`という名前で、以下のようになります:

```javascript
Script.Signals.UpdateSelection = function () {
updateSelection ();
}

リピーターの出力シグナルSelection Changed(リストアイテム内のCheckboxからトリガーされる)をこの新しいシグナルに接続することができます。また、Multi Select ListSelectionsArrayComponent Outputに接続します。

異なるチェックボックスをクリックしてみて、SelectionArrayがそれに応じて変更されることを確認してください。

シグナルのタイミング

選択が変更されるたびにイベントを送信し、新しい選択Arrayとともに送信する必要があります。RepeaterノードからのSelection Changedシグナルを直接Component Outputに接続すると、Scriptノードがシグナルがトリガーされるまでに実行されているかどうかを確認できません。場合によってはそうかもしれませんし、そうでないかもしれません。したがって、Multi Select ListSelection Changedシグナルを聞いている人が新しい選択を読む場合(非常にありそうなケース)、前のArrayを読む可能性があります。

解決策は、これらのイベントをチェーンすることです。Selection Changedイベントは、Scriptノード上のUpdateSelectionシグナルをトリガーする必要があります。次に、新しい選択Arrayが生成されると、Scriptからシグナルを送信する必要があり、それがコンポーネントからの出力シグナルとして使用されます。

スクリプトからシグナルを送信する

そのシグナルを追加しましょう。Scriptノードをクリックします。新しい出力を追加します。SelectionChangedと名付けます。

次に、それがSignalとして設定されていることを確認します。

今、私たちはスクリプト内から関数Script.Outputs.<シグナル名> ()を呼び出すことによってシグナルを送信できます。この場合、updateSelection関数にそれを追加します:

Component.Object.Selection = Component.Object.Checkboxes.filter(o => o.Checked)
.map(o => Noodl.Object.create({
id:Component.Object.id+"_"+o.Value,
Value:o.Value}
));
Script.Outputs.SelectionChanged ();

新しい出力SelectionChangedMulti Select ListComponent OutputsSelection Changed入力に接続できます。

マルチセレクトリストの仕上げ

残っているのは、全て選択/選択解除機能を実装することだけです。これは非常に簡単に行えます。Scriptノードに2つの入力シグナル、Script.Signals.SelectAllScript.Signals.DeselectAllを追加します。

Scriptノードでこれらの関数を追加することは簡単で、完全なコードは以下のようになります:

function updateSelection () {
Component.Object.Selection = Component.Object.Checkboxes.filter(o => o.Checked)
.map(o => Noodl.Object.create({
id:Component.Object.id+"_"+o.Value,
Value:o.Value}
));
Script.Outputs.SelectionChanged ();
}

Script.Setters.Items = function (items) {
if(!Script.Inputs.Items) return
// 初期選択配列に基づいてチェックされたアイテムのリストを作成します
Component.Object.Checkboxes = Script.Inputs.Items.map(o => Noodl.Object.create({
id:Component.Object.id+'-'+o.id,
Value:o.id,
Checked:Script.Inputs.InitialSelection!==undefined && !!Script.Inputs.InitialSelection.find(s => s.Value === o.id)
}))

updateSelection ();
}

Script.Signals.UpdateSelection = function () {
updateSelection ();
}

Script.Signals.SelectAll = function () {
if(!Component.Object.Checkboxes) return

Component.Object.Checkboxes.forEach ( o => o.Checked = true);
updateSelection ();
}

Script.Signals.DeselectAll = function () {
if(!Component.Object.Checkboxes) return

Component.Object.Checkboxes.forEach ( o => o.Checked = false);
updateSelection ();
}

もちろん、Component InputsのポートSelect AllClear Selectionからこれらの関数をトリガーする必要があります。

マルチセレクトリストは完成しました。次に、それを使用して実行したいマルチセ

レクト操作を構築します。

マルチセレクト操作の追加

最初に説明したように、マージ削除、_コピー_の操作を実装したいと考えています。また、全て選択または全選択解除も可能にしたいと考えています。したがって、UIをいくつか構築する必要があります。Checkboxと3つのButtonsで構成されるトップバーを作成します。可能な限りシンプルにします。マージンとパディングを追加して読みやすくするために、水平に配置されたGroupノードを使用します。自分で構築したくない場合は、以下のノードをコピーして貼り付けることができます。

マルチセレクトリストに選択と選択解除のロジックを接続することができます。実装した2つのシグナルをトリガーするために、Switchを使用します。

マルチセレクトリストで全て選択/選択解除の実装が正しく機能するかテストできます。

さらに多くのコンポーネントオブジェクト

これからのスクリプトでマージ/コピー/削除操作を実装するために、選択状態を保存してスクリプトで簡単にアクセスできるようにする必要があります。再びComponent Objectを使用します。選択の保存操作には、Set Component Object Propertiesノードを使用できます。ノードを追加し、Selectionプロパティを追加します。Arrayタイプに設定します。

Multi Select Listコンポーネントから出力されるSelectionプロパティをそれに接続します。選択が変更されるたびに設定する必要があるため、Selection ChangedDoに接続します。

Functionノードを使用した軽量スクリプティング

Buttonsにはそれ自体に少しビジネスロジックが必要です。選択されたアイテムがない場合はアイテムを無効にする必要があります。また、_マージ_操作には少なくとも2つのアイテムが選択されている必要があります。

これをJavascriptで書く場合、Scriptノードは少し重すぎます - 実際には1つの関数しかありません。代わりにFunctionノードを使用します。

メインアプリにFunctionノードを追加し、Calculate Button Statesと名付けます。また、DeleteEnabledCopyEnabledMergeEnabledという3つの出力を追加します。Booleanタイプにする必要があります。なぜなら、それらをButtonsEnabledプロパティに直接接続したいからです。

次に、Functionノードを開いて、コードを直接書き込みます。Functionノードでは、実際のコード以外に何も書く必要はありません。つまり、関数宣言などは必要ありません。

コードは以下のようになります:

Outputs.CopyEnabled = Component.Object.Selection !== undefined && Component.Object.Selection.length > 0;
Outputs.DeleteEnabled = Component.Object.Selection !== undefined && Component.Object.Selection.length > 0;
Outputs.MergeEnabled = Component.Object.Selection !== undefined && Component.Object.Selection.length > 1;

出力はOutputs.<出力名>を使用してアクセスし、入力(使用している場合)はInputs.<入力名>を介してアクセスします。また、Component Objectの使用も見て取れます。

Functionノードは、入力が変更されるたびに実行されます。ただし、Runシグナルが接続されている場合は、Runがトリガーされたときにのみ実行されます。このケースでは、入力がないため、Runをトリガーする必要があります。Component.Object.Selectionが有効で設定された状態でトリガーすることを確実にするために、Set Component Object PropertiesからのDoneRunに接続します。また、Functionノードからの出力をButtonsに接続します。

ロジックをテストして、Buttonsが適切に有効/無効になることを確認してください。

リスト操作の実装

ほぼ完了です。実際の操作をScriptノードで実装するだけです。その前に、リストのアイテムを保持するArrayをどのように扱うかについて少し考える必要があり

ます。現在、アイテムはStatic Arrayから直接取得しています。これは、コピー/削除/マージを開始すると機能しなくなります。Scriptがそれを取得し、コピー/削除/マージが発生した後に新しいArrayに更新できる場所にArrayを保存する必要があります。

Component Objectが救世主です!Component Objectノードを作成し、Itemsプロパティを追加します。次に、Static ArrayからのItemsをそれに接続します。Component ObjectからMulti Select Listコンポーネントにも接続します。

これで、リスト操作スクリプトを書く準備が整いました。新しいScriptノードを追加します。3つの関数を以下に実装します:


/* 選択されたアイテムのみを含む新しい配列を返します */
function getSelectedItems () {
if (Component.Object.Selection === undefined) {
// 選択がない場合、空の配列を返します
return [];
}
else {
return Component.Object.Selection.map ( (o) => Noodl.Object.get (o.Value));
}
}


/* 選択されたアイテムを除くすべてのアイテムを含む新しい配列を返します */

function removeSelectedItems () {

return Component.Object.Items.filter ( (o) => {
// このアイテムが選択リストにあるかどうかを確認します
if (Component.Object.Selection === undefined) {
// 選択がない場合、アイテムはリストに残すべきです
return true;
}
else {
// このアイテムが選択リストにない場合はtrueを返します(つまり、アイテムを保持します)
return Component.Object.Selection.find (p => (p.Value === o.id)) === undefined;
}
});
}

/*
選択されたすべてのアイテムを1つのアイテムにマージします。
最新の日付を持つアイテムを新しいアイテムとして使用し、
他のアイテムを削除します。 */

Script.Signals.Merge = function () {
if (Component.Object.Items !== undefined && Component.Object.Selection !== undefined) {
// 選択されたアイテムから始めます
let itemsToMerge = getSelectedItems();
// 最新の日付でソートします
let sortedItems = itemsToMerge.sort ( (o,p) => {
return new Date (p.delivery_date) - new Date (o.delivery_date);
});
// ソートされたリストの最初のアイテムは、保持したいアイテムです
let itemToKeep = sortedItems[0];

// マージするアイテムの数量の合計を計算します
let sumOfQuantities = sortedItems.reduce ( (total , o) => total + o.quantity, 0);
// そして、保持するアイテムにそれを保存します
itemToKeep.quantity = sumOfQuantities;
// 選択されたアイテムをすべて削除します
let itemsToKeep = removeSelectedItems ();
// 保持するアイテムを追加します
itemsToKeep.push (itemToKeep);
// これが新しいアイテムです!
Component.Object.Items = itemsToKeep;
}

}

Script.Signals.Copy = function () {
if (Component.Object.Items !== undefined && Component.Object.Selection !== undefined) {
let selectedItems = getSelectedItems ();
// 選択されたすべてのアイテムを取得します
// 選択内の各アイテムに対して新しいオブジェクトを作成し、値をコピーします
// order_nbrに"-2"を追加します
let copiedItems = selectedItems.map ( (o) => {
return Noodl.Object.create ({
order_nbr: o.order_nbr+"-2",
quantity: o.quantity,
delivery_date: o.delivery_date
});
});
Component.Object.Items = Component.Object.Items.concat (copiedItems);
}
}

Script.Signals.Delete = function () {
if (Component.Object.Items !== undefined && Component.Object.Selection !== undefined) {
Component.Object.Items = removeSelectedItems ();
}
}

コードをScriptノードに貼り付けます。Component.Object.Itemsは常に新しいArrayに置き換えられ、古いものを変更することはありません。これは、マルチセレクトリストが更新する必要があることを理解するためです。

最後に、各Buttonsを対応するリスト操作に接続して完了です!

完全なプロジェクトを以下からインポートできます。