やりたいこと
OutSystemsでスクリーン上にカウントダウンタイマーを表示します。
こんなの
概要
JavaScriptのSetInterval
を利用して、1秒おきにスクリーンのexpressionに設定してあるLocal Variableを更新します。
仕組みはこれだけ。単純です。
プログラムの構成
Screen
Local Variable
-
InputSec
- Integer
- ユーザが入力するカウントダウン秒数。
- InputウィジェットのVariableに指定。
-
CountdownSec
- Integer
- 実際のカウントダウンで更新される秒数。
InputSec
がコピーされて1秒おきに1ずつ減らされる。 - Expressionに指定。
- Default Value:
-1
Expression
- カウントダウン数値表示
Substr("0" + Max(CountdownSec, 0), Length("0" + Max(CountdownSec, 0)) - 2, 2)
-
CountdownSec
は初期表示時-1
なので、マイナス値を0
に変換してから利用。 - 何となく"00"表示にしたかったので、左に0パディングしています。
- "Finished!"表示
If(CountdownSec = 0, "Finished!", "")
- 実行中は表示したくないため、
CountdownSec
が0
の場合のみ表示。この条件だけだと初回表示時も「Finished!」などとアホくさい表示がドヤ顔でされてしまうため、CountdownSec
を初期値-1
にし、0
になるのは実行完了した時だけになるようにしました。
その他
- 細かいことですが、[Start]ボタンのEnabledに
CountdownSec <= 0
を指定することでカウントダウン実行中はボタンが使用不可となり、多重起動を防止することができます。
Client Action
StartOnClick
-
InputSec
のバリデーション(1以上であること) -
CountdownSec
にInputSec
をAssign - JavaScript呼び出し
JavaScript
console.log("[START]");
console.log($parameters.CountdownSec);
const timerId = setInterval(() => {
if ($parameters.CountdownSec > 0) {
$parameters.CountdownSec--;
$actions.CountdownExec();
console.log($parameters.CountdownSec);
} else {
clearInterval(timerId);
console.log("[END]");
}
}, 1000);
JavaScrip Input Parameter
-
CountdownSec
- Integer
- Local Variable
InputSec
またはCountdownSec
を指定。どちらも直前のAssignで同値になっているのでどっちでもいいです。
SetInterval
を引数1000ms
で呼び出すことで1秒おきに処理を発生させ、その中で受け取ったパラメータ値をデクリメントしつつClient Actionを呼び出します。実際の表示用Local Variable CountdownSec
は呼び出したClient Action CountdownExec
の中で更新します。
パラメータ値が0
になったら処理は終了。この時、SetInterval
呼び出しで作成されるtimer id
を使ってclearInterval
を実行します。
仕組みはこれだけ。単純です。
CountdownExec
Local Variable CountdownSec
を-1するだけです。超絶単純です。
が、何故わざわざこいつをJavaScriptから呼び出すなどという面倒なことをしているのかというと、JavaScript内でアクセスできるOutSystemsの要素が限定されているためです。変数として扱えるのはInput Parameterとして与えられたもののみで直接Local Variableなどをいじることはできません。
このため、今回は不要ですが処理結果を後続処理で使いたい場合はOutput Parameterに詰めて送ってやる必要があります。
注意点
SetInterval
を実行したままで画面遷移やブラウザを閉じるなどしてスクリーンが失われると、以下のエラーが発生します。
Invalid call of the '#ClientActionName' client action of the '#ScreenName' since the latter is not currently active. This is likely due to a platform's client action being used as an event handler or in a setTimeout function. Consider removing this call by using the 'On Destroy' event of the screen/block or moving your logic to a global client action.
完全な対応策はないようですが、以下のようにForumでは何度も話題に上がっています。
Invalid call of the 'xyz' client action of the 'Screen'. error Log
今回の場合はエラーログの言う通りOnDestroy
でclearInterval(timerId);
を実行してやれば解決!……すればいいのですが、この場合もブラウザやタブを閉じてしまった場合はService Centerにエラーが記録されてしまいます(OnDestroy
は画面遷移の最後に発生するイベントなので)。こちらの対応策としてはJavaScriptのbeforeUnload
イベントなどが考えられますが、これも完全な対応策とは言い切れません。現状は無視するしか方法はないのかな、と思います。
ちなみに、これに一時停止/再開機能を付けようとすると割とかなり相当めんどくさいと思われます。悪しからずご了承ください。