AngularJSで、時間のかかる通信の間にモーダルを使ってプログレスバーを表示する
JavaScript 2014年3月18日時間のかかる非同期通信を行っている間は、通信中であることをユーザーに知らせつつユーザーによる画面操作を禁止したほうが良いことがある。UI Bootstrapのモーダルとプログレスバーでこれを行う方法を示す
モダンな Webアプリケーションはいわゆる Ajaxという方式でサーバと非同期で通信することで画面に表示するための情報を取得したりユーザーの入力をサーバに送信したりする。同期通信と異なり、非同期通信の場合は通信中もユーザーインターフェイスがユーザーと対話出来るため、通信中にユーザーが画面操作を行うことにより別のアクションを起こされては困る場合には特別の配慮が必要である。
例えば、クレジットカードの決済画面でユーザーが送信ボタンを押しサーバとの通信が行われている数秒の間に送信ボタンが再度押されたり、メニューを操作して別の画面に飛ばれたりしてしまっては困るといった具合だ。クレジットカード決済のような重要な局面でなくても、通信が完了する前に別の画面操作が行われてしまっては困るケースは結構ある。
時間のかからない通信に関しても油断は出来ない。ユーザーがモバイル端末を利用している場合、通信状況によってはサーバのレスポンスが返ってくるのに意外と時間がかかるかもしれない。かといって Ajax通信を行う全ての場面でこの問題に配慮するのは難しいと思うが、少なくともクリティカルであることが先にわかっているものについては対処しておくべきだろう。
以前に AngularJSで HTML5的なアラートウィンドウを表示するという記事で、UI Bootstrap を使って AngularJSから Bootstrapのモーダルウィンドウを利用する方法を紹介しているが、表示中は元の画面の操作が禁止されるというモーダルウィンドウの性質と、Bootstrapのプログレスバーコンポーネントを利用して前述の問題へ対応する方法を紹介する。
レスポンスが返るのに時間のかかるAPI
時間のかかるAPI呼び出しをシミュレートするため、あえて5秒間なにもせずに待ってからレスポンスを返すだけの APIを作成した。
// Scala + Spring Framework
@RequestMapping(value=Array("api/somethingtakestime"), method=Array(RequestMethod.GET))
def somethingtakestime():Map[String,Any] = {
Thread.sleep(5000) // 5秒ただ時間をつぶす
Map("result"->"通信成功")
}
スクリーンショット
通信開始ボタンを押すとレスポンスに時間のかかるAPIを呼び出す。
通信中の様子。モーダルが表示され、プログレスバーのストライプが動く。背景がグレーになっていることから想像できるように、この間ユーザーは画面操作を行えない。
本来プログレスバーといえば進捗状況を表示するものだが、サーバ側の処理が実際にどこまで進捗しているかを知る方法が無いのと、ここではユーザーに「何か時間のかかる処理をしているので待って欲しい」という意思表示を明確に出来れば良いので進捗を100%に固定している。
通信が終わるとモーダルはクローズされ、ユーザーは画面操作を行うことができるようになる。
サンプルソース
通信を開始する直前に $modalサービスを使ってモーダルを画面に表示する。モーダルを後でクローズできるように、開いたモーダルのインスタンスは変数に格納しておく。
通信には例によって $resourceサービス を使用している。APIリソースに対して get/query/save/deleteのようなリクエストを実行する際には引数として通信完了時の処理を成功時・失敗時のそれぞれについて渡すことが出来るので、いずれの場合であってもモーダルを閉じるように記述する。
<html lang="ja" ng-app="MyApp">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="http://netdna.bootstrapcdn.com/bootstrap/latest/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.14/angular.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.14/angular-resource.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.10.0/ui-bootstrap-tpls.min.js"></script>
<script language="javascript">
angular.module("MyApp", ["ngResource","ui.bootstrap"])
.run(["$rootScope","$resource","$modal", function($scope,$resource,$modal) {
$scope.startCommunication = function() {
// 通信開始直前にプログレスバーを含むモーダルをオープンする
var modalInstance = $modal.open({
templateUrl:"progress.html",
backdrop:"static",keyboard:false// ユーザーがクローズできないようにする
});
// 通信開始
$scope.data = $resource("api/somethingtakestime").get(
{},
function() { // 通信成功時
modalInstance.close();// モーダルを閉じる
},
function() { // 通信失敗時
modalInstance.close();// 失敗の時もちゃんとモーダルを閉じる
$scope.data = {result: "通信エラー"}// 何らかのエラー処理
}
);
}
}]);
</script>
<title>時間のかかる通信の間にプログレスバーを表示する</title>
</head>
<body>
<div class="container">
<h1>時間のかかる通信の間にプログレスバーを表示する</h1>
<button class="btn btn-primary" ng-click="startCommunication()">通信開始</button>
{{data.result}}
</div>
<!-- プログレスバーを表示するモーダル -->
<script type="text/ng-template" id="progress.html">
<div class="modal-header">
通信中...
</div>
<div class="modal-body">
<!-- ストライプ模様のバーをグルグルさせる。伸びるアニメーションは無し。
進捗は(サーバ側の進捗をクライアント側で知る術はないので)100%に固定 -->
<progressbar class="progress-striped active" animate="false" value="100">
</progressbar>
</div>
</script>
</body>
</html>
同じカテゴリの記事
Angular.JSでselect要素にoptionをぶら下げる色々な方法 2014-03-29HTML5アプリケーションでの、サーバとクライアントの時差について 2014-03-17
AngularJSの $resourceを使って application/x-www-form-urlencoded 形式のリクエストを POSTする 2014-03-15
AngularJSと UI Bootstrapで日付と時刻の入力をする 2014-03-14
お勧めカテゴリ
なじみ深い日本製アニメの英語版DVDで、字幕と音声から英語を学びましょうという趣旨のシリーズ記事です。
Scalaと Spring Frameworkを使って REST的なJSON APIを実装してみましょう。
代表 嶋田大貴のブログです。写真は神仏に見せ金をはたらく罰当たりの図