Blog
Tutorial (basic learning) » 09. Sample Video Searching Application

09. Sample Video Searching Application

Last modified by kashi on 2015/01/30, 13:09

Overview

ここでは、基本偏のまとめとしてYoutubeのAPIを使用して動画を検索し一覧表示するアプリケーションを作成します。

Specifications

view.png

キーワードを入力して、検索ボタンを押下すると関連する動画をリスト表示します。
検索~一覧表示でよくみられる以下のポイントを押さえます。

  1. 非同期で検索する
    • 指定されたキーワードを元に、関連する動画を6件取得する。(非同期処理)
    • 一覧取得中は、処理中を表すインジケータを表示し、ユーザの操作をブロックする。
  2. 結果をテンプレートを使用して表示する
    • 各行のHTMLはテンプレート化されており、サーバから取得したデータを合わせて表示する。
  3. 表示負荷が高い内容は、可視範囲のみ表示する
    • 取得した全ての動画に対して再生用プレイヤーを1度に埋めこむと重いので可視範囲内のものだけ表示する(非同期処理)
  4. 遅延ロード
    • 下までスクロールした場合次の6件を取得する。(非同期処理)

Structure of HTML

bind.png

画面のHTML構造は以下のようになっています。

  • 赤枠はコントローラをバインドする要素
  • 青枠は検索ボタン押下時にsubmitイベントが発生するform要素
  • 緑枠は検索結果を表示するul要素(テンプレートを使って結果を表示する箇所)

Processes

検索ボタン押下時、スクロールイベント発生時による処理の流れをシーケンス図で表します。
2つで共通する処理は以下の通りです。

  • youtube-controller
    • _searchメソッド
      • 検索ロジック(youtube-logicのsearchメソッド)を実行し、結果を画面へ表示します
    • _addPlayerInView
      • 可視範囲にあるプレイヤー表示div(class="video-frame")を取得し、プレイヤー表示処理(_addPlayer)を呼び出します
      • プレイヤー表示div(class="video-frame")は検索後、テンプレートを使用して画面へ表示されます
    • _addPlayer
      • iframe内に、テンプレートを使用してプレイヤーを表示します
  • youtube-logic
    • searchメソッド
      • 非同期処理でYouTubeからキーワードに関連する動画を検索します

When Clicking the submit button

submit.png

  1. submitイベント
    • 検索ボタンを押下すると画面からsubmitイベントが発生します
    • youtube-controllerのsubmitイベントに定義された処理(submitイベントハンドラ)が実行されます
  2. _search
    • 入力されたキーワードのチェックをして問題ない場合のみ検索処理(_search)を呼び出します
  3. search()
    • youtube-logicの検索処理を呼び出します
  4. h5.ajax
    • 非同期でYouTubeのサーバからキーワードに紐つく動画情報を検索します
  5. 検索結果を返す
    • 結果をqXhrオブジェクト(Promiseオブジェクトの性質を備えているオブジェクト)で返します
  6. 検索結果をリスト表示
    • 検索完了後、youtube-controllerの_searchメソッドで結果をテンプレートを使用してリスト表示します
  7. _addPlayerInView
    • 可視範囲にあるiframeを取得し、プレイヤー表示処理を呼び出します
  8. _addPlayer
    • テンプレートを使用してプレイヤーを表示します
  9. 可視範囲のプレイヤーを表示
    • 可視範囲のプレイヤーが表示されます

Delay Loading(Show next results when scrolling)

scroll.png

  1. scrollイベント
    • 画面をスクロールするとスクロールイベントが発生します
    • youtube-controllerはスクロールイベントに定義されたスクロール処理を実行します
  2. _addPlayerInView
    • 可視範囲にあるiframeを取得し、プレイヤー表示処理を呼び出します
  3. _addPlayer
    • テンプレートを使用してプレイヤーを表示します
  4. 可視範囲のプレイヤーを表示
    • 可視範囲のプレイヤーが表示されます
  5. _search
    • 入力されたキーワードのチェックをして問題ない場合のみ検索処理(_search)を呼び出します
  6. search()
    • youtube-logicの検索処理を呼び出します
  7. h5.ajax
    • 非同期でYouTubeのサーバからキーワードに紐つく動画情報を検索します
  8. 検索結果を返す
    • 結果をqXhrオブジェクト(Promiseオブジェクトの性質を備えているオブジェクト)で返します
  9. 検索結果をリスト表示
    • 検索完了後、youtube-controllerの_searchメソッドで結果をテンプレートを使用してリスト表示します

Implemention

ここでは以下のようなStepでアプリの完成をめざします。

Step1 HTMLを用意しよう
Step2 コントローラを書いてみよう
Step3 ロジックを書いてみよう
Step4 コントローラとロジックを連携させよう
Step5 結果を画面へ反映させよう
Step6 遅延ロードを実装してみよう

各Stepでの作業手順は以下の通りです。

  1. お題を確認する
  2. TODOを確認する
  3. コードを記述する
  4. 動作確認を行い期待通りに動くことを確認する
  5. 完成版と自分が書いたコードを見比べる

必要最低限のコードを記述していますので、前回までの基礎を思い出しながらコードを書いてみましょう。
わからなくなったら、参考リンクをたどったり完成版のコードを参照してください。

完成版のコードと見比べるときは、自分のコードと違う箇所はないか?違う箇所の意味は?など考えながら比べましょう。

Step1 HTML

まず、土台となるHTMLを用意して表示して見ましょう

Main Topic

YouTube検索アプリHTMLを記述し画面を表示しましょう

TODO

  1. 下記の参考HTMLをstep9.htmlというファイル名で保存してください
  2. 必要なライブラリを読み込んでください
    1. jQuery、hifive(h5.css, ejs-h5mod.js, h5.dev.js)が必要です
    2. 以降で作成するyoutube-controller.jsやyoutube-logic.jsも適時読み込んでください

Example:HTML

<!doctype html>
<html>
   <head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta charset="UTF-8">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Cache-Control" content="no-cache">
<meta http-equiv="Expires" content="-1">
<meta name="viewport" content="width=device-width">

       <!-- 必要なライブラリを適時読み込んでください -->

       <title>hifive YouTube検索サンプル</title>
   </head>
   <body>
       <!-- header -->
       <div class="navbar navbar-inverse">
           <div class="container">
               <p class="navbar-brand" >hifive YouTube検索サンプル</p>
           </div>
       </div>
       <!-- container -->
       <div id="container" class="container theme-showcase">
           <!-- 検索窓 -->
           <div class="page-title jumbotron">
               <p>キーワードを入力し検索ボタンを押下すると、関連する動画を表示します。</p>
               <form id="search" class="search-form corner-all search">
                   <div class="search search-form-content">
                       <input type="text" id="keyword" class="corner-all form-control search-keyword" placeholder="キーワード"/>
                   </div>
                   <div class="search-form-content">
                       <input type="submit" class="corner-all btn btn-info" value="検索"/>
                   </div>
               </form>
           </div>
           <!-- 検索結果 -->
           <div class="page-header">
               <h3 class="result-message">検索結果: <span id="totalCount">0</span></h3>
               <ul id="result" class="result-lists list-group"></ul>
           </div>
       </div>
   </body>
</html>

Test

YouTube検索アプリ画面がエラー無く表示されることを確認しましょう

Step2 Controller

次に、hifiveの基礎であるコントローラを記述して画面からのイベントを受け取って処理をしましょう

Main Topic

検索ボタンをクリックしてテキストボックスの入力値をイベントハンドラで取得しましょう

TODO

  1. コントローラを記述しましょう
  2. formのsubmitイベントハンドラを書いてみましょう

Processes

step1.png

  1. 検索ボタンを押下するとsubmitイベントが起きます
  2. youtubeControllerのsubmitイベントハンドラが実行されます
  3. submitイベントハンドラで入力値をalert表示します

Create Controller

  • 参考:コントローラ
  • youtube-controller.jsというファイル名で作成後、HTMLで読み込んでください。
  • __nameプロパティは、youtube.sample.YoutubeController にしてください。

Create a Submit Event Handler

formのsubmitイベントハンドラを書いてみましょう( 参考:イベントハンドラの基本構文

  • イベントハンドラ内で入力値をalert表示してみましょう。ただし、入力値が未入力または長さ0の場合は何もしないようにしてください。
  • デフォルトのイベント動作をキャンセルしましょう。(参考:デフォルトのイベント動作をキャンセルする
    • formのsubmitイベントが行われるとフォームの送信(画面遷移)が行われますが今回は画面遷移の必要がないのでデフォルトのイベント動作をキャンセルしましょう

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

(function($) {

   /**
     * コントローラ
     *
     * @name YoutubeController
     * @namespace
     */

   var youtubeController = {

       /**
         * コントローラ名
         *
         * @memberOf youtube.sample.YoutubeController
         */

        __name: 'youtube.sample.YoutubeController',
      
       /**
         * 検索キーワード
         *
         * @memberOf youtube.sample.YoutubeController
         */

        _keyword: '',

       /**
         * 「検索」ボタン押下時に実行するハンドラ
         * <p>
         * 検索結果・件数の表示部分をクリアし、youtubeからユーザが入力したキーワードが含まれる動画情報を取得します。 取得した情報からDOM要素生成し、検索結果として一覧表示します。
         *
         * @param {Object} context コンテキスト
         * @param {jQuery} $el イベントターゲット要素(form)
         * @memberOf youtube.sample.YoutubeController
         */

       // formのsubmitイベントのイベントハンドラを定義
       '#search submit': function(context, $el) {

           // submitイベントのデフォルト動作をキャンセルする
           context.event.preventDefault();

           // 画面に入力されたキーワード取得
           var $keywordInput = this.$find('#keyword');
           this._keyword = $keywordInput.val();

           // キーワードチェック(未入力または長さ0の場合は何もしない)
           if (!this._keyword || $.trim(this._keyword).length === 0) {
               return;
            }
           //キーワード表示
           alert(this._keyword);
        }
    };
    h5.core.expose(youtubeController);
})(jQuery);
$(function(){
    h5.core.controller('#container', youtube.sample.YoutubeController);
})    

Test

キーワードを入力し、検索ボタンを押下すると入力値がalert表示されることを確認しましょう。

Step3 Logic

次は、非同期処理を記述して実行結果を確認できるようにしましょう

Main Topic

非同期処理でYouTubeから動画を検索してみよう

TODO

  1. ロジックを作成しよう
  2. ロジックに非同期処理を書いてみよう
    1. YouTube Data API を確認しよう
    2. ロジックに非同期処理を行うメソッド(search)を追加しましょう
  3. ロジックを実行して結果を確認しよう

Processes

step2.png

  1. ロジック内のsearchメソッドでキーワードに関連する動画を検索します(非同期処理)
  2. 検索結果が返ります

Create Logic

  • 参考:ロジックの定義
  • youtube-logic.jsというファイルを作成後、HTMLで読み込んでください
  • ロジックの__nameプロパティは、youtube.sample.YoutubeLogic にしてください

YouTube Data API

  • YouTubeで動画を検索するには以下URLにリクエストを送信します
  • 必要なリクエストパラメータは以下の通りです
    • vq : キーワード
    • max-results : 何件取得するか(結果の最大件数)
    • alt : レスポンスのフォーマット(今回は’json-in-script’を指定)
    • start-index : 検索開始インデックス(1始まり)
  • レスポンスの中身は以下の通りです
    • feed.entry:結果リスト(取得した動画情報が入ったリスト)
      • 以下はfeed.entryの1要素の内容です。「.」つなぎでアクセスできます
        • id.$t:動画ID
        • media$group.media$title.$t:タイトル
        • yt$statistics.viewCount :視聴数
        • yt$statistics.favoriteCount:お気に入り数
        • media$group.media$description.$t:動画説明文
    • feed.openSearch$totalResults.$t:総件数

Add a search method to execute an asynchronous process

  • h5.ajaxメソッドはjQuery.ajaxメソッドをラップしたものです。戻り値はjqXhrオブジェクトです。
    • jqXhrオブジェクトはPromiseオブジェクトの性質を備えているので、そのままコントローラに戻り値として渡すことで非同期処理の制御が可能です。
  • h5.ajaxに設定するパラメータは以下の通りです
    • url : リクエストを送信するURL(今回はYouTubeのURL)
    • data : 指定したURLのリクエストパラメータ(今回はYouTubeのリクエストパラメータ)をオブジェクトで指定する
    • dataType : リクエストのフォーマット(今回は’jsonp’を指定する)
    • cache : 通信結果をキャッシュするかどうか(true:キャッシュする false:キャッシュしない)今回はキャッシュする

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

(function($) {
   /**
     * Youtubeビデオ検索URL
     */

   var URL = 'http://gdata.youtube.com/feeds/api/videos';

   /**
     * YoutubeLogic
     *
     * @class
     * @name youtube.sample.YoutubeLogic
     */

   var youtubeLogic = {

       /**
         * ロジック名
         *
         * @memberOf youtube.sample.YoutubeLogic
         */

        __name: 'youtube.sample.YoutubeLogic',

       /**
         * 指定された条件でyoutubeのフィードにリクエストを送る
         *
         * @memberOf youtube.sample.YoutubeController
         * @param {String} keyword キーワード
         * @param {Number} startIndex 開始インデックス
         * @param {Number} maxResults 何件取得するか
         * @returns {jqXHR} jqXHRオブジェクト
         */

        search: function(keyword, startIndex, maxResults) {
           // 非同期通信でyoutubeへアクセスする
           var promise = h5.ajax(URL, {
                dataType: 'jsonp',
                data: {
                   'vq': keyword,
                   'max-results': maxResults,
                   'alt': 'json-in-script',
                   'start-index': startIndex
                },
                cache: true
            });
           // Promiseオブジェクト(jqXHRオブジェクト)を返す
           return promise;
        }
    };
   //ロジックを単体で実行できるように公開する
   h5.core.expose(youtubeLogic);
})(jQuery);  

Test

ロジックを実行して結果を確認しましょう。
ロジックを公開(expose)しておくと、ロジック単体で実行することができます。

  1. ChromeブラウザでF12を押下して開発者ツールを開きConsoleを開きましょう
  2. Consoleで、以下コマンドを入力しロジックを実行しましょう
    youtube.sample.YoutubeLogic.search('hifive', 1, 6).done(function(data) { console.log(data.feed.openSearch$totalResults.$t)});
    • YoutubeLogicのsearchメソッドへ引数を渡し総件数を表示するコマンドです
    • 引数にはキーワード('hifive')、開始index(1)、何件取得するか(6)を指定しています
    • 検索処理完了後に総件数(data.feed.openSearch$totalResults.$t)をコンソールへ表示します
      logic_console.png
  3. 以下のような結果がコンソールに表示されることを確認しましょう
    logic_exit.png
  4. Networkタグでは検索結果のJSONを確認することができます
    logic_exit_network.png

Step4 Combine Controller and Logic

次は、コントローラとロジックを連結させてみましょう。

Main Topic

検索ボタンを押下してロジックを実行してみよう

TODO

  1. youtubeController内に、検索処理(_search)を作成しよう
  2. 検索処理からロジックの非同期処理(search)を実行し、総件数をalert表示しよう
  3. 検索処理の実行中は画面全体にインジケータを表示しよう

Processes

step3.png

  1. _searchを呼び出す
    • youtubeController内に、_searchメソッド(検索処理)を作成します
    • submitイベントハンドラで、入力値が未入力でない場合に_searchメソッドを呼び出します(引数として入力値を渡します)
  2. logicのsearchを呼び出す
    • _searchメソッドから、youtubeLogicのsearchメソッドを呼び出します。(引数として入力値、検索開始index、検索件数を渡します)
    • コントローラからロジックを呼び出す場合は、コントローラでロジックの宣言が必要になります。忘れずに宣言しましょう。
    • 参照:ロジックの定義
  3. Promiseオブジェクトを受け取る
  4. 総件数をalert表示
  5. 検索中はインジケータを表示する
    • 検索中は画面全体にインジケータを表示します
    • インジケータのオプションプロパティのpromiseにPromiseオブジェクトを指定すると通信が終了すると同時にインジケータを画面から除去します
    • 参考:indicator API
    • 参考:インジケータサンプル

Declare Logic

コントローラからロジックを参照する場合はロジックの宣言が必要です。
以下のように、コントローラのプロパティとして追加しましょう。

   /**
     * YoutubeLogicを宣言
     *
     * @memberOf youtube.sample.YoutubeController
     */

    _youtubeLogic: youtube.sample.YoutubeLogic,

Search Process(_search)

以下コードの「NUMBER_TO_LOAD_AT_ONCE」は一度に読み込む件数の定数です。(6が設定されています)


       /**
         * キーワードに関連する動画を検索します
         * <p>
         * 検索ロジックを実行し、結果を画面へ表示します。
         *
         * @memberOf youtube.sample.YoutubeController
         * @param {String} keyword キーワード
         * @param {Number} index 検索開始インデックス
         * @param {String | Object} indicatorTarget
         *            インジケータを表示する対象(セレクタまたはjQueryオブジェクトまたはDOMオブジェクトを指定)
         * @param {String} indicatorMessage インジケータに表示するメッセージ
         * @returns {Promise} promiseオブジェクト
         */

        _search: function(keyword, index, indicatorTarget, indicatorMessage) {
           
           //検索ロジックが返すpromiseオブジェクトを変数に保持する
           var promise = this._youtubeLogic.search(keyword, index, NUMBER_TO_LOAD_AT_ONCE);

           //検索ロジック完了
           promise.done(function(data) {
               //総件数を表示
               alert(data.feed.openSearch$totalResults.$t);
            });
           
           //検索中はインジケータ表示(検索ロジックが返すpromiseオブジェクトを設定する)
           this.indicator({
                target: indicatorTarget,
                message: indicatorMessage,
                promises: promise
            }).show();

           // promiseオブジェクトを返す
           return promise;
        },

Test

  • 以下の通り動作することを確認しましょう
    • キーワード入力→検索ボタン押下→画面全体にインジケータ表示→インジケータが非表示になる→総件数がalert表示される

Step5 Show Results in Your Display

次は、検索結果をテンプレート使用して画面へ表示してみましょう

Main Topic

検索ボタンをクリックして検索した結果を画面へ表示しよう

TODO

  1. 検索結果リストを表示しよう
    1. 検索結果リストを表示するテンプレートを作成しよう
    2. 検索完了後、テンプレートを使用して結果を画面へ表示しよう
  2. 総件数を表示しよう(テンプレート未使用)

Processes

step4.png

  1. 検索結果をリスト表示
    • テンプレートを使用するには、テンプレートのロードが必要です。忘れずに読み込みましょう(参考:テンプレートのロード
    • 検索完了後、promise.doneに登録する関数内でテンプレートを使用して結果を画面へ反映させましょう(参考:テンプレートに対する操作
    • promise.doneに登録する関数内でコントローラを指すthisを使う場合は、工夫が必要です。関数をthis.ownで囲む必要があります(参考:own API
  2. 総件数を表示する
    • _searchの結果をpromiseオブジェクトで受け取り、総件数をspanタグ(id="totalCount")のテキストとして表示します。

Create a Template of Search Results

以下のような検索結果を表示するテンプレートを作成しましょう。
template_image.png
なお、このテンプレートを画面に反映した時点ではプレイヤーは表示されません。

  • list.ejsというファイル名で保存しましょう
  • テンプレートは<script type="text/ejs"></script>で囲んだ中に記述します
  • テンプレートidは‘list’です
    • <script>タグのidで指定した「list」が、this.view.get()で呼び出すときに必要なテンプレートIDとなります
  • JSPと同じように、テンプレートの[% %]または[%= %]の位置にjavascriptコードを記述することができます
  • 参考:テンプレートの基本構文

このテンプレートでは、結果リスト(entry)をfor文でループし、1要素(1動画情報)を1つのliタグ内に表示しています。

<% /*---------- 検索結果リスト ----------*/ %>
<script type="text/ejs" id="list">
[% for (var i = 0, len = entry.length; i < len; i++) {
    //動画情報グループを取得
    var group = entry[i].media$group;
    //動画タイトル取得
    var title = group.media$title.$t;
    //動画ID取得(動画のURL)
    var id = entry[i].id.$t;

    //視聴数のundefinedチェック(undefinedの場合は0を設定する)
     var viewCount;
    if( undefined === entry[i].yt$statistics || undefined === entry[i].yt$statistics.viewCount ){
        viewCount = 0;
    }else{
        viewCount = entry[i].yt$statistics.viewCount;
    }

    //お気に入り数のundefinedチェック(undefinedの場合は0を設定する)
     var favoriteCount;
    if( undefined === entry[i].yt$statistics || undefined === entry[i].yt$statistics.favoriteCount ){
        favoriteCount = 0;
    }else{
        favoriteCount = entry[i].yt$statistics.favoriteCount;
    }

    //動画の説明文取得
    var description = $.trim(group.media$description.$t);

    //プレイヤーのURL作成
    var player = playerUrl + id.substring(id.lastIndexOf('/'), id.length);
 %]
<li class="list-group-item">
    <div>
        <!-- プレイヤー表示領域 -->
        <div class="video-container">
            <div class="video-frame">
                <!-- プレイヤーのurlを表示する -->
                <input type="hidden" class="videoSrc" value="[%= player %]" />
            </div>
        </div>
        <!-- 動画情報表示 -->
        <div class="summary">
            <div class="titleContainer">
                <a class="title" href="[%= group.media$player[0].url %]" title="[%= title %]">[%= title %]</a>
            </div>
            <div class="count">
                <span class="viewCount">[視聴数]</span> [%= viewCount %]
                <span class="favoriteCount">[お気に入り数]</span> [%= favoriteCount %]
            </div>
            <div class="description">
                [%= description %]
            </div>
        </div>
        <div style="clear:both"></div>
    </div>
</li>
[% } %]
</script>

コントローラでテンプレートを使用するには、テンプレートの読み込みが必要です。
以下コードは検索結果テンプレート(list.ejs)の読み込みです。コントローラのプロパティに追加しましょう。

   /**
     * 使用するテンプレート
     *
     * @memberOf youtube.sample.YoutubeController
     */

    __templates: './list.ejs',

Show Results Using the Template

_searchメソッドの検索完了後に結果表示処理を追加しましょう

  • 検索結果を表示するDOMは<ul id=“result“>です
  • テンプレートidは‘list’です
  • テンプレート内で使用する変数は以下をオブジェクトで指定します
    • entry : 結果リスト(今回は、data.feed.entryを指定)
    • playerUrl:YouTubeプレイヤーのurl(今回は‘http:www.youtube.com/embed’を指定)

以下コードの「YOUTUBE_PLAYER_URL」はYouTubeプレイヤーのURLの定数です。('http://www.youtube.com/embed'が設定されています)

   //検索ロジック完了
   promise.done(this.own(function(data) {

       //検索結果をリスト表示
       this.view.append('#result', 'list', {
            entry: data.feed.entry,
            playerUrl: YOUTUBE_PLAYER_URL
        });
       
    }));

Total Count

submitイベントハンドラで、検索処理の結果(_searchの結果)をpromiseオブジェクトで受け取り、
総件数をspanタグ(id="totalCount")のテキストとして表示します。

Add a Player

次に、検索結果リストにプレイヤーを追加してみましょう

TODO

  1. プレイヤーを表示しよう
    1. プレイヤーを表示するテンプレートを作成しよう
    2. 検索結果リスト表示後、テンプレートを使用してプレイヤーを表示しよう
    3. プレイヤー表示中は、プレイヤー表示領域にインジケータを表示しよう

Processes

step4-2.png

プレイヤー表示の処理の流れは以下の通りです。

  1. プレイヤーURL表示
    • 検索結果リストが画面へ表示されると、非表示のinputタグ(class="videoSrc")にプレイヤーのurlが表示されます
  2. _addPlayerInView
    • 可視範囲にあるプレイヤー表示領域を取得します(参考:h5.ui.isInView
  3. _addPlayer
    • 可視範囲にある場合のみ、テンプレートを使用してプレイヤーを表示します
    • プレイヤーの表示にはプレイヤーurlが必要です。
    • テンプレートidは‘player’です
    • テンプレートで使用する変数は以下をオブジェクトで指定します
      • src : プレイヤーurl
  4. インジケータ表示
    • プレイヤー表示中はプレイヤー表示領域divタグ(class="video-container")にインジケータを表示します

プレイヤーテンプレートの作成

プレイヤーのテンプレートはlist.ejsの末尾に追加しましょう。(参考:テンプレートの複数記述)

<% /*---------- ビデオプレイヤー ----------*/ %>
<script type="text/ejs" id="player">
<iframe class="video" src="[%= src %]" frameborder="0" allowfullscreen></iframe>
</script>

Show the Player

プレイヤー表示のコードは以下です。
_searchメソッドの検索完了後(promise.doneに登録する関数内)に_addPlayerInViewを呼び出しましょう。

以下コードの「VIDEO_LOADED_CLASS」はプレイヤー読み込み済みクラスの定数です。('videoLoaded'が設定されています)

       /**
         * 可視範囲にあるvideoframeへプレイヤーを埋め込みます
         *
         * @memberOf youtube.sample.YoutubeController
         */

        _addPlayerInView: function() {
           //プレイヤー読み込み済みクラスが設定されていないdivを取得する
           var $players = this.$find('div.video-frame:not(div.' + VIDEO_LOADED_CLASS + ')');
           if (!$players.length) {
               return;
            }

           //可視範囲内のプレイヤーを埋め込む
           $players.each(this.own(function(i, player) {
               if (h5.ui.isInView(player)) {
                   this._addPlayer(player);
                }
            }));
        },

       /**
         * プレイヤー表示用コンテナにプレイヤーを埋め込みます
         *
         * @memberOf youtube.sample.YoutubeController
         * @param {Object} player プレイヤー読み込み用DOM
         */

        _addPlayer: function(player) {

           var $target = $(player);

           //videwのURLが設定されていない場合処理しない
           var $videoSrc = $target.find('input.videoSrc');
           if (!$videoSrc.length) {
               return;
            }

           //プレイヤーを読み込む
           this.view.update(player, 'player', {
                src: $videoSrc.val()
            });

           //プレイヤー読み込み済みのクラスを追加する
           $target.addClass(VIDEO_LOADED_CLASS);

           //videoをload中にインジケータを表示する
           var $videoContener = $target.parent();
           var indicator = this.indicator({
                target: $videoContener
            }).show();

           //videoがload完了後、インジケータを非表示にする
           var $videoIFrame = $target.children('iframe');
            $videoIFrame.load(function() {
                indicator.hide();
            });
        },

Test

以下の通り動作するか確認しましょう
キーワード入力→ 検索ボタン押下→画面全体にインジケータ表示→インジケータが非表示になる→総件数が画面に表示される→結果リストが画面に表示される

Step6 Delay Loading

最後に、スクロールイベントに処理を定義してみましょう。
ここでは、遅延ロードと呼ばれる手法を実装します。

  • 遅延ロード
    • 必要なとき(今回であれば可視範囲が移動した場合、つまりスクロール時)に必要な情報をロードすることで負荷を分散しブラウザが固まることを防ぐ手法

残っている処理は以下2つです。2つとも今までに作成したメソッドを呼ぶことで実現できます。

  1. 残りのプレイヤーを表示する
    1. Step5までの処理で可視範囲のプレイヤーは表示されました。下にスクロールすると今まで可視範囲外だった領域にはプレイヤーが表示されていないので表示する必要があります。
  2. 残りの動画情報を検索して表示する
    1. 表示件数が総件数より少ない場合は、残りの動画を再度検索して表示しましょう
    2. 検索中は検索結果リストの最後にインジケータを表示しましょう。(インジケータを表示する領域はテンプレートで追加しましょう)

Main Topic

遅延ロードを実装してみよう

TODO

  1. スクロールイベントハンドラを追加しよう
  2. スクロールバーの最下部からの位置 > 50の場合、 残りのプレイヤーを表示しよう
  3. スクロールバーの最下部からの位置 <= 50の場合、残りの動画情報を検索して表示しよう
    1. 検索中は検索結果リストの最後尾にインジケータを表示しよう(テンプレート使用)

スクロールイベントハンドラの追加

まずは、スクロールイベントに処理を定義してみましょう。
スクロールイベントが発生する要素はdocumentです。documentのイベントハンドラの記述方法は特殊ですので以下を参考にしてください。

参考:特別なオブジェクト(window, document等)へのイベントハンドラの設定

   /**
     * documentでscrollイベント時に実行するハンドラ
     * <p>
     * リストの末尾に新しい検索結果を追加します。
     *
     * @param {Object} context コンテキスト
     * @param {jQuery} $el イベントターゲット要素(イベントを発生させた要素)
     * @memberOf youtube.sample.YoutubeController
     */

   // documentのscrollイベントのイベントハンドラを定義
   '{document} [scroll]': function(context, $el) {
       //処理
   }

Calculate the Scroll Position

スクロールの位置によって実行する処理を分けましょう。

  1. スクロールバーの最下部からの位置 > 50の場合、 残りのプレイヤーを表示
  2. スクロールバーの最下部からの位置 <= 50の場合、残りの動画情報を検索して表示

スクロールイベントハンドラの第二引数「$el」はイベントを発生させた要素、つまりスクロールバーです。
スクロールの位置計算に利用しましょう。

   //スクロールバーの最下部からの位置を求める
   var scrollTop = $el.scrollTop();
   var scrollHeight = document.documentElement.scrollHeight || document.body.scrollHeight;
   var clientHeight = window.innerHeight;
   var remain = scrollHeight - (scrollTop + clientHeight);

残りのプレイヤーを表示する

スクロールイベントハンドラ内に残りのプレイヤー表示処理を追加しましょう。
プレイヤー表示処理は、スクロールイベント毎に実行されると負荷が高いのでsetTimeout関数で少し間を空けて実行しましょう。

以下コードの「SCROLL_DELAY」はスクロール停止と判断するまでの間隔の定数です。(単位はms。500が設定されています。)
「_timer」はタイマー用変数です。コントローラのプロパティとして保持します。

       // タイマー(this._timer)をクリアする
       if (this._timer) {
            clearTimeout(this._timer);
        }
       //可視範囲内のプレイヤーを埋める(scroll毎に処理が走ると負荷が高いので、タイマー関数で少し間を空けて実行する)
       this._timer = setTimeout(this._addPlayerInView(), SCROLL_DELAY);

Search and Show the Rest of Movie Information

スクロールイベントハンドラ内に動画情報を検索し表示する処理を追加しましょう。

繰り返し検索を行うので、検索を開始するインデックス(_nextSearchIndex)をコントローラのプロパティで保持しておきましょう。
また、総件数(_totalCount)もプロパティで保持しておきましょう。

以下コードの「NUMBER_TO_LOAD_AT_ONCE」は一度に読み込む件数の定数です。(6が設定されています)

       //次に読み込むindexを加算する
       this._nextSearchIndex += NUMBER_TO_LOAD_AT_ONCE;

       // 総件数より大きくなった場合は、検索を行わない
       if (this._totalCount <= this._nextSearchIndex) {
           return;
        }

       // videoを検索する
       var promise = this._search(this._keyword, this._nextSearchIndex, '#indicatorSpace', '');

About an Indicator

scroll_indicator.png
残りの動画を検索中は、検索結果リスト(id="result")の最後にインジケータを表示しましょう。
インジケータを表示する領域(liタグ)はテンプレートで追加しましょう。今回はテンプレートへ引数を渡す必要はありません。

テンプレートは以下の通りです。list.ejsの末尾に追加しましょう。

<% /*---------- インジケータ ----------*/ %>
<script type="text/ejs" id="indicator_space">
<li class="list-group-item" id="indicatorSpace">
<div class="listContainer"></div>
</li>

インジケータは常に検索結果リストの末尾に表示するので、検索前に結果リストに追加(this.view.append)し、
検索が完了後にインジケータ表示領域をDOMから削除しましょう。


       //インジケータ表示用領域を取得
       var $indicatorSpace = this.$find('#indicatorSpace');
       if ($indicatorSpace.length === 0) {
           // インジケータ表示用領域がなければ、遅延ロードインジケータ表示領域を追加
           this.view.append('#result', 'indicator_space');
        }

       // videoを検索する
       var promise = this._search(this._keyword, this._nextSearchIndex, '#indicatorSpace', '');

       //検索完了
       promise.done(this.own(function() {
           // インジケータ表示用領域削除
           $indicatorSpace.remove();
        }));

Test

以下の通り動作するか確認しましょう(検索結果表示後から記述しています)
下方向にスクロールする→今まで可視範囲外だったプレイヤーが表示される→一番下までスクロールする→結果リストの末尾にインジケータが表示される→検索結果が末尾に追加される

Complete Version

サンプルでは見た目をよくするためにbootstrapと、自前のCSS(youtube.css)を読み込んでいます。

YouTube検索サンプル

次のステップ⇒チュートリアル10.スマートフォン対応(jQueryMobileとの連携)


Copyright (C) 2012-2017 NS Solutions Corporation, All Rights Reserved.