إنشاء نموذج تطبيق

ترشدك هذه الصفحة خلال خطوات إنشاء تطبيق يستخدم عدة واجهات برمجة تطبيقات مختلفة لإعداد إحصاءات المشاهدة لمقاطع فيديو المستخدم على YouTube. ينفِّذ التطبيق المهام التالية:

  • وهي تستخدم واجهة برمجة تطبيقات البيانات في YouTube لاسترداد قائمة مقاطع الفيديو المحمّلة بواسطة المستخدم الذي تمت المصادقة عليه حاليًا ثم عرض قائمة بعناوين الفيديو.
  • عندما ينقر المستخدم على فيديو معين، يستدعي التطبيق واجهة برمجة تطبيقات YouTube Analytics لاسترداد بيانات التحليلات لهذا الفيديو.
  • يستخدم التطبيق واجهة برمجة تطبيقات الرسوم المرئية في Google لتخطيط بيانات analytics.

توضح الخطوات التالية عملية إنشاء التطبيق. في الخطوة الأولى، يمكنك إنشاء ملفات HTML وCSS الخاصة بالتطبيق. تصف الخطوات من 2 إلى 5 أجزاءً مختلفة من جافا سكريبت يستخدمها التطبيق. ويتم أيضًا تضمين نموذج الرمز الكامل في نهاية المستند.

  1. الخطوة 1: إنشاء صفحة HTML وملف CSS
  2. الخطوة 2: تفعيل مصادقة OAuth 2.0
  3. الخطوة 3: استرداد بيانات المستخدم الذي سجّل الدخول حاليًا
  4. الخطوة 4: طلب بيانات "إحصاءات Google" لفيديو معيّن
  5. الخطوة 5: عرض بيانات "إحصاءات Google" في رسم بياني

ملاحظة مهمة: يجب تسجيل تطبيقك في Google للحصول على معرِّف عميل OAuth 2.0 لتطبيقك.

الخطوة 1: إنشاء صفحة HTML وملف CSS

في هذه الخطوة، ستنشئ صفحة HTML تحمّل مكتبات جافا سكريبت التي سيستخدمها التطبيق. تعرض شفرة HTML أدناه شفرة الصفحة:


<!doctype html>
<html>
<head>
  <title>Google I/O YouTube Codelab</title>
  <link type="text/css" rel="stylesheet" href="index.css">
  <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
  <script type="text/javascript" src="//www.google.com/jsapi"></script>
  <script type="text/javascript" src="index.js"></script>
  <script type="text/javascript" src="https://apis.google.com/js/client.js?onload=onJSClientLoad"></script>
</head>
<body>
  <div id="login-container" class="pre-auth">This application requires access to your YouTube account.
    Please <a href="#" id="login-link">authorize</a> to continue.
  </div>
  <div class="post-auth">
    <div id="message"></div>
    <div id="chart"></div>
    <div>Choose a Video:</div>
    <ul id="video-list"></ul>
  </div>
</body>
</html>

وكما هو موضّح في علامة <head> لنموذج الصفحة، يستخدم التطبيق المكتبات التالية:

  • يوفِّر jQuery طرقًا مساعدًا لتبسيط عبور مستندات HTML والتعامل مع الأحداث والصور المتحركة وتفاعلات Ajax.
  • يتيح لك أداة التحميل إلى Google API (www.google.com/jsapi) استيراد واحدة أو أكثر من Google APIs. يستخدم نموذج التطبيق هذا أداة تحميل واجهة برمجة التطبيقات لتحميل واجهة برمجة التطبيقات للتمثيل البصري في Google، والتي تُستخدم لتخطيط بيانات Analytics التي تم استردادها.
  • تحتوي مكتبة index.js على وظائف خاصة بنموذج التطبيق. يرشدك البرنامج التعليمي هذا إلى خطوات إنشاء هذه الدوال.
  • تساعدك مكتبة عميل "Google APIs" للغة JavaScript على تنفيذ مصادقة OAuth 2.0 واستدعاء واجهة برمجة تطبيقات "إحصاءات YouTube".

يتضمن نموذج التطبيق أيضًا ملف index.css. يتم عرض نموذج ملف CSS، الذي يمكنك حفظه في نفس صفحة HTML، أدناه:


body {
  font-family: Helvetica, sans-serif;
}

.pre-auth {
  display: none;
}

.post-auth {
  display: none;
}

#chart {
  width: 500px;
  height: 300px;
  margin-bottom: 1em;
}

#video-list {
  padding-left: 1em;
  list-style-type: none;
}
#video-list > li {
  cursor: pointer;
}
#video-list > li:hover {
  color: blue;
}

الخطوة 2: تفعيل مصادقة OAuth 2.0

في هذه الخطوة، ستبدأ في إنشاء ملف index.js الذي يتم استدعاؤه بواسطة صفحة HTML. من هذا المنطلق، أنشئ ملفًا باسم index.js في الدليل نفسه الذي توجد فيه صفحة HTML وأدخل الرمز التالي في ذلك الملف. استبدِل السلسلة YOUR_CLIENT_ID بمعرِّف العميل للتطبيق المسجَّل.

(function() {

  // Retrieve your client ID from the Google API Console at
  // https://console.cloud.google.com/.
  var OAUTH2_CLIENT_ID = 'YOUR_CLIENT_ID';
  var OAUTH2_SCOPES = [
    'https://www.googleapis.com/auth/yt-analytics.readonly',
    'https://www.googleapis.com/auth/youtube.readonly'
  ];

  // Upon loading, the Google APIs JS client automatically invokes this callback.
  // See https://developers.google.com/api-client-library/javascript/features/authentication 
  window.onJSClientLoad = function() {
    gapi.auth.init(function() {
      window.setTimeout(checkAuth, 1);
    });
  };

  // Attempt the immediate OAuth 2.0 client flow as soon as the page loads.
  // If the currently logged-in Google Account has previously authorized
  // the client specified as the OAUTH2_CLIENT_ID, then the authorization
  // succeeds with no user intervention. Otherwise, it fails and the
  // user interface that prompts for authorization needs to display.
  function checkAuth() {
    gapi.auth.authorize({
      client_id: OAUTH2_CLIENT_ID,
      scope: OAUTH2_SCOPES,
      immediate: true
    }, handleAuthResult);
  }

  // Handle the result of a gapi.auth.authorize() call.
  function handleAuthResult(authResult) {
    if (authResult) {
      // Authorization was successful. Hide authorization prompts and show
      // content that should be visible after authorization succeeds.
      $('.pre-auth').hide();
      $('.post-auth').show();

      loadAPIClientInterfaces();
    } else {
      // Authorization was unsuccessful. Show content related to prompting for
      // authorization and hide content that should be visible if authorization
      // succeeds.
      $('.post-auth').hide();
      $('.pre-auth').show();

      // Make the #login-link clickable. Attempt a non-immediate OAuth 2.0
      // client flow. The current function is called when that flow completes.
      $('#login-link').click(function() {
        gapi.auth.authorize({
          client_id: OAUTH2_CLIENT_ID,
          scope: OAUTH2_SCOPES,
          immediate: false
        }, handleAuthResult);
      });
    }
  }

  // This helper method displays a message on the page.
  function displayMessage(message) {
    $('#message').text(message).show();
  }

  // This helper method hides a previously displayed message on the page.
  function hideMessage() {
    $('#message').hide();
  }
  /* In later steps, add additional functions above this line. */
})();

الخطوة 3: استرداد بيانات المستخدم الذي سجّل الدخول حاليًا

في هذه الخطوة، ستضيف دالة إلى ملف index.js الذي يسترد خلاصة مقاطع الفيديو المحمّلة حاليًا والتي سجّلها المستخدم باستخدام واجهة برمجة تطبيقات البيانات في YouTube (الإصدار 2.0). ستحدد هذه الخلاصة معرّف قناة المستخدم على YouTube، والذي ستحتاج إليه عند استدعاء واجهة برمجة تطبيقات YouTube Analytics. بالإضافة إلى ذلك، سيسرد نموذج التطبيق الفيديوهات التي حمّلها المستخدم ليتمكّن المستخدم من استرداد بيانات "إحصاءات Google" لأي فيديو فردي.

أجرِ التغييرات التالية على ملف index.js:

  1. أضف وظيفة تحمّل واجهة العميل لـ YouTube Analytics وواجهات برمجة تطبيقات البيانات. ويعد هذا مطلبًا أساسيًا لاستخدام برنامج جافا سكريبت Google APIs.

    بعد تحميل واجهتي عميل واجهة برمجة التطبيقات، تستدعي الدالة الدالة getUserChannel.

    
      // Load the client interfaces for the YouTube Analytics and Data APIs, which
      // are required to use the Google APIs JS client. More info is available at
      // https://developers.google.com/api-client-library/javascript/dev/dev_jscript#loading-the-client-library-and-the-api
      function loadAPIClientInterfaces() {
        gapi.client.load('youtube', 'v3', function() {
          gapi.client.load('youtubeAnalytics', 'v1', function() {
            // After both client interfaces load, use the Data API to request
            // information about the authenticated user's channel.
            getUserChannel();
          });
        });
      }
    
  2. أضِف المتغيّر channelId بالإضافة إلى الدالة getUserChannel. تستدعي الدالة واجهة برمجة التطبيقات لبيانات YouTube (الإصدار 3) وتتضمّن المعلمة mine التي تشير إلى أن الطلب متعلق بمعلومات قناة المستخدم الذي تمت مصادقته حاليًا. سيتم إرسال channelId إلى واجهة برمجة تطبيقات "إحصاءات Google" لتحديد القناة التي تسترد بيانات "إحصاءات Google" عنها.

    
      // Keep track of the currently authenticated user's YouTube channel ID.
      var channelId;
    
      // Call the Data API to retrieve information about the currently
      // authenticated user's YouTube channel.
      function getUserChannel() {
        // Also see: https://developers.google.com/youtube/v3/docs/channels/list
        var request = gapi.client.youtube.channels.list({
          // Setting the "mine" request parameter's value to "true" indicates that
          // you want to retrieve the currently authenticated user's channel.
          mine: true,
          part: 'id,contentDetails'
        });
    
        request.execute(function(response) {
          if ('error' in response) {
            displayMessage(response.error.message);
          } else {
            // We need the channel's channel ID to make calls to the Analytics API.
            // The channel ID value has the form "UCdLFeWKpkLhkguiMZUp8lWA".
            channelId = response.items[0].id;
            // Retrieve the playlist ID that uniquely identifies the playlist of
            // videos uploaded to the authenticated user's channel. This value has
            // the form "UUdLFeWKpkLhkguiMZUp8lWA".
            var uploadsListId = response.items[0].contentDetails.relatedPlaylists.uploads;
            // Use the playlist ID to retrieve the list of uploaded videos.
            getPlaylistItems(uploadsListId);
          }
        });
      }
    
  3. أضف الدالة getPlaylistItems، التي تسترد العناصر في قائمة تشغيل محددة. في هذه الحالة، تسرد قائمة التشغيل مقاطع الفيديو المحمّلة إلى قناة المستخدم. (تجدر الإشارة إلى أن نموذج النموذج أدناه لا يسترد سوى أول 50 عنصرًا في تلك الخلاصة، وستحتاج إلى تطبيق التقسيم على صفحات لجلب عناصر إضافية.)

    بعد استرداد قائمة عناصر قائمة التشغيل، تستدعي الدالة الدالة getVideoMetadata(). بعد ذلك، تحصل هذه الدالة على بيانات وصفية حول كل فيديو في القائمة وتضيف كل فيديو إلى القائمة التي يراها المستخدم.

    
      // Call the Data API to retrieve the items in a particular playlist. In this
      // example, we are retrieving a playlist of the currently authenticated user's
      // uploaded videos. By default, the list returns the most recent videos first.
      function getPlaylistItems(listId) {
        // See https://developers.google.com/youtube/v3/docs/playlistitems/list
        var request = gapi.client.youtube.playlistItems.list({
          playlistId: listId,
          part: 'snippet'
        });
    
        request.execute(function(response) {
          if ('error' in response) {
            displayMessage(response.error.message);
          } else {
            if ('items' in response) {
              // The jQuery.map() function iterates through all of the items in
              // the response and creates a new array that only contains the
              // specific property we're looking for: videoId.
              var videoIds = $.map(response.items, function(item) {
                return item.snippet.resourceId.videoId;
              });
    
              // Now that we know the IDs of all the videos in the uploads list,
              // we can retrieve information about each video.
              getVideoMetadata(videoIds);
            } else {
              displayMessage('There are no videos in your channel.');
            }
          }
        });
      }
    
      // Given an array of video IDs, this function obtains metadata about each
      // video and then uses that metadata to display a list of videos.
      function getVideoMetadata(videoIds) {
        // https://developers.google.com/youtube/v3/docs/videos/list
        var request = gapi.client.youtube.videos.list({
          // The 'id' property's value is a comma-separated string of video IDs.
          id: videoIds.join(','),
          part: 'id,snippet,statistics'
        });
    
        request.execute(function(response) {
          if ('error' in response) {
            displayMessage(response.error.message);
          } else {
            // Get the jQuery wrapper for the #video-list element before starting
            // the loop.
            var videoList = $('#video-list');
            $.each(response.items, function() {
              // Exclude videos that do not have any views, since those videos
              // will not have any interesting viewcount Analytics data.
              if (this.statistics.viewCount == 0) {
                return;
              }
    
              var title = this.snippet.title;
              var videoId = this.id;
    
              // Create a new <li> element that contains an <a> element.
              // Set the <a> element's text content to the video's title, and
              // add a click handler that will display Analytics data when invoked.
              var liElement = $('<li>');
              var aElement = $('<a>');
              // Setting the href value to '#' ensures that the browser renders the
              // <a> element as a clickable link.
              aElement.attr('href', '#');
              aElement.text(title);
              aElement.click(function() {
                displayVideoAnalytics(videoId);
              });
    
              // Call the jQuery.append() method to add the new <a> element to
              // the <li> element, and the <li> element to the parent
              // list, which is identified by the 'videoList' variable.
              liElement.append(aElement);
              videoList.append(liElement);
            });
    
            if (videoList.children().length == 0) {
              // Display a message if the channel does not have any viewed videos.
              displayMessage('Your channel does not have any videos that have been viewed.');
            }
          }
        });
      }
    

الخطوة 4: طلب بيانات Analytics لمقطع فيديو

في هذه الخطوة، ستغيّر نموذج التطبيق بحيث عندما ينقر التطبيق على عنوان الفيديو، يستدعي التطبيق واجهة برمجة تطبيقات YouTube Analytics لاسترداد بيانات Analytics لهذا الفيديو. ولإجراء ذلك، عليك إجراء التغييرات التالية على نموذج التطبيق:

  1. أضف متغيرًا يحدد النطاق الزمني الافتراضي لبيانات تقارير Analytics التي تم استردادها.

    
      var ONE_MONTH_IN_MILLISECONDS = 1000 * 60 * 60 * 24 * 30;
    
  2. أضِف رمزًا ينشئ سلسلة YYYY-MM-DD لكائن التاريخ ويضع أرقام اليوم والشهر في التواريخ إلى رقمَين:

    
      // This boilerplate code takes a Date object and returns a YYYY-MM-DD string.
      function formatDateString(date) {
        var yyyy = date.getFullYear().toString();
        var mm = padToTwoCharacters(date.getMonth() + 1);
        var dd = padToTwoCharacters(date.getDate());
    
        return yyyy + '-' + mm + '-' + dd;
      }
    
      // If number is a single digit, prepend a '0'. Otherwise, return the number
      //  as a string.
      function padToTwoCharacters(number) {
        if (number < 10) {
          return '0' + number;
        } else {
          return number.toString();
        }
      }
    
  3. حدد الدالة displayVideoAnalytics التي تسترد بيانات YouTube Analytics لمقطع فيديو. سيتم تنفيذ هذه الدالة عندما ينقر المستخدم على فيديو في القائمة. تحدد الدالة getVideoMetadata، التي تطبع قائمة الفيديوهات وتم تحديدها في الخطوة 3، معالج حدث النقر.

    
      // This function requests YouTube Analytics data for a video and displays
      // the results in a chart.
      function displayVideoAnalytics(videoId) {
        if (channelId) {
          // To use a different date range, modify the ONE_MONTH_IN_MILLISECONDS
          // variable to a different millisecond delta as desired.
          var today = new Date();
          var lastMonth = new Date(today.getTime() - ONE_MONTH_IN_MILLISECONDS);
    
          var request = gapi.client.youtubeAnalytics.reports.query({
            // The start-date and end-date parameters must be YYYY-MM-DD strings.
            'start-date': formatDateString(lastMonth),
            'end-date': formatDateString(today),
            // At this time, you need to explicitly specify channel==channelId.
            // See https://developers.google.com/youtube/analytics/v1/#ids
            ids: 'channel==' + channelId,
            dimensions: 'day',
            sort: 'day',
            // See https://developers.google.com/youtube/analytics/v1/available_reports
            // for details about the different filters and metrics you can request
            // if the "dimensions" parameter value is "day".
            metrics: 'views',
            filters: 'video==' + videoId
          });
    
          request.execute(function(response) {
            // This function is called regardless of whether the request succeeds.
            // The response contains YouTube Analytics data or an error message.
            if ('error' in response) {
              displayMessage(response.error.message);
            } else {
              displayChart(videoId, response);
            }
          });
        } else {
          // The currently authenticated user's channel ID is not available.
          displayMessage('The YouTube channel ID for the current user is not available.');
        }
      }
    

    اطّلع على صفحة التقارير المتاحة في وثائق واجهة برمجة التطبيقات لمزيد من المعلومات عن البيانات التي يمكن استردادها ومجموعات القيم الصالحة للمعلَمات metrics وdimensions وfilters.

الخطوة 5: عرض بيانات Analytics في مخطط

في هذه الخطوة، ستضيف الدالة displayChart التي ترسل بيانات YouTube Analytics إلى واجهة برمجة التطبيقات للتمثيل البصري في Google. بعد ذلك، تنشئ واجهة برمجة التطبيقات هذه رسمًا بيانيًا للمعلومات.

  1. حمّل واجهة برمجة تطبيقات الرسوم المرئية في Google، والتي ستعرض بياناتك في رسم بياني. راجِع مستندات واجهة برمجة التطبيقات للتمثيل البصري للاطّلاع على مزيد من التفاصيل حول خيارات الرسم البياني.

    google.load('visualization', '1.0', {'packages': ['corechart']});
    
  2. حدد دالة جديدة تُسمى displayChart تستخدم واجهة برمجة تطبيقات الرسوم المرئية في Google لإنشاء رسم بياني يعرض بيانات Analytics ديناميكيًا.

    
      // Call the Google Chart Tools API to generate a chart of Analytics data.
      function displayChart(videoId, response) {
        if ('rows' in response) {
          hideMessage();
    
          // The columnHeaders property contains an array of objects representing
          // each column's title -- e.g.: [{name:"day"},{name:"views"}]
          // We need these column titles as a simple array, so we call jQuery.map()
          // to get each element's "name" property and create a new array that only
          // contains those values.
          var columns = $.map(response.columnHeaders, function(item) {
            return item.name;
          });
          // The google.visualization.arrayToDataTable() function wants an array
          // of arrays. The first element is an array of column titles, calculated
          // above as "columns". The remaining elements are arrays that each
          // represent a row of data. Fortunately, response.rows is already in
          // this format, so it can just be concatenated.
          // See https://developers.google.com/chart/interactive/docs/datatables_dataviews#arraytodatatable
          var chartDataArray = [columns].concat(response.rows);
          var chartDataTable = google.visualization.arrayToDataTable(chartDataArray);
    
          var chart = new google.visualization.LineChart(document.getElementById('chart'));
          chart.draw(chartDataTable, {
            // Additional options can be set if desired as described at:
            // https://developers.google.com/chart/interactive/docs/reference#visdraw
            title: 'Views per Day of Video ' + videoId
          });
        } else {
          displayMessage('No data available for video ' + videoId);
        }
      }
    

الاطلاع على ملف index.js الكامل

يتضمن ملف index.js أدناه جميع التغييرات من الخطوات الموضحة أعلاه. تذكر مرة أخرى أنه يلزمك استبدال السلسلة YOUR_CLIENT_ID بالرقم التعريفي للعميل للتطبيق المسجل.


(function() {
  // Retrieve your client ID from the Google API Console at
  // https://console.cloud.google.com/.
  var OAUTH2_CLIENT_ID = 'YOUR_CLIENT_ID';
  var OAUTH2_SCOPES = [
    'https://www.googleapis.com/auth/yt-analytics.readonly',
    'https://www.googleapis.com/auth/youtube.readonly'
  ];

  var ONE_MONTH_IN_MILLISECONDS = 1000 * 60 * 60 * 24 * 30;

  // Keep track of the currently authenticated user's YouTube channel ID.
  var channelId;

  // For information about the Google Chart Tools API, see:
  // https://developers.google.com/chart/interactive/docs/quick_start
  google.load('visualization', '1.0', {'packages': ['corechart']});

  // Upon loading, the Google APIs JS client automatically invokes this callback.
  // See https://developers.google.com/api-client-library/javascript/features/authentication 
  window.onJSClientLoad = function() {
    gapi.auth.init(function() {
      window.setTimeout(checkAuth, 1);
    });
  };

  // Attempt the immediate OAuth 2.0 client flow as soon as the page loads.
  // If the currently logged-in Google Account has previously authorized
  // the client specified as the OAUTH2_CLIENT_ID, then the authorization
  // succeeds with no user intervention. Otherwise, it fails and the
  // user interface that prompts for authorization needs to display.
  function checkAuth() {
    gapi.auth.authorize({
      client_id: OAUTH2_CLIENT_ID,
      scope: OAUTH2_SCOPES,
      immediate: true
    }, handleAuthResult);
  }

  // Handle the result of a gapi.auth.authorize() call.
  function handleAuthResult(authResult) {
    if (authResult) {
      // Authorization was successful. Hide authorization prompts and show
      // content that should be visible after authorization succeeds.
      $('.pre-auth').hide();
      $('.post-auth').show();

      loadAPIClientInterfaces();
    } else {
      // Authorization was unsuccessful. Show content related to prompting for
      // authorization and hide content that should be visible if authorization
      // succeeds.
      $('.post-auth').hide();
      $('.pre-auth').show();

      // Make the #login-link clickable. Attempt a non-immediate OAuth 2.0
      // client flow. The current function is called when that flow completes.
      $('#login-link').click(function() {
        gapi.auth.authorize({
          client_id: OAUTH2_CLIENT_ID,
          scope: OAUTH2_SCOPES,
          immediate: false
        }, handleAuthResult);
      });
    }
  }

  // Load the client interfaces for the YouTube Analytics and Data APIs, which
  // are required to use the Google APIs JS client. More info is available at
  // https://developers.google.com/api-client-library/javascript/dev/dev_jscript#loading-the-client-library-and-the-api
  function loadAPIClientInterfaces() {
    gapi.client.load('youtube', 'v3', function() {
      gapi.client.load('youtubeAnalytics', 'v1', function() {
        // After both client interfaces load, use the Data API to request
        // information about the authenticated user's channel.
        getUserChannel();
      });
    });
  }

  // Call the Data API to retrieve information about the currently
  // authenticated user's YouTube channel.
  function getUserChannel() {
    // Also see: https://developers.google.com/youtube/v3/docs/channels/list
    var request = gapi.client.youtube.channels.list({
      // Setting the "mine" request parameter's value to "true" indicates that
      // you want to retrieve the currently authenticated user's channel.
      mine: true,
      part: 'id,contentDetails'
    });

    request.execute(function(response) {
      if ('error' in response) {
        displayMessage(response.error.message);
      } else {
        // We need the channel's channel ID to make calls to the Analytics API.
        // The channel ID value has the form "UCdLFeWKpkLhkguiMZUp8lWA".
        channelId = response.items[0].id;
        // Retrieve the playlist ID that uniquely identifies the playlist of
        // videos uploaded to the authenticated user's channel. This value has
        // the form "UUdLFeWKpkLhkguiMZUp8lWA".
        var uploadsListId = response.items[0].contentDetails.relatedPlaylists.uploads;
        // Use the playlist ID to retrieve the list of uploaded videos.
        getPlaylistItems(uploadsListId);
      }
    });
  }

  // Call the Data API to retrieve the items in a particular playlist. In this
  // example, we are retrieving a playlist of the currently authenticated user's
  // uploaded videos. By default, the list returns the most recent videos first.
  function getPlaylistItems(listId) {
    // See https://developers.google.com/youtube/v3/docs/playlistitems/list
    var request = gapi.client.youtube.playlistItems.list({
      playlistId: listId,
      part: 'snippet'
    });

    request.execute(function(response) {
      if ('error' in response) {
        displayMessage(response.error.message);
      } else {
        if ('items' in response) {
          // The jQuery.map() function iterates through all of the items in
          // the response and creates a new array that only contains the
          // specific property we're looking for: videoId.
          var videoIds = $.map(response.items, function(item) {
            return item.snippet.resourceId.videoId;
          });

          // Now that we know the IDs of all the videos in the uploads list,
          // we can retrieve information about each video.
          getVideoMetadata(videoIds);
        } else {
          displayMessage('There are no videos in your channel.');
        }
      }
    });
  }

  // Given an array of video IDs, this function obtains metadata about each
  // video and then uses that metadata to display a list of videos.
  function getVideoMetadata(videoIds) {
    // https://developers.google.com/youtube/v3/docs/videos/list
    var request = gapi.client.youtube.videos.list({
      // The 'id' property's value is a comma-separated string of video IDs.
      id: videoIds.join(','),
      part: 'id,snippet,statistics'
    });

    request.execute(function(response) {
      if ('error' in response) {
        displayMessage(response.error.message);
      } else {
        // Get the jQuery wrapper for the #video-list element before starting
        // the loop.
        var videoList = $('#video-list');
        $.each(response.items, function() {
          // Exclude videos that do not have any views, since those videos
          // will not have any interesting viewcount Analytics data.
          if (this.statistics.viewCount == 0) {
            return;
          }

          var title = this.snippet.title;
          var videoId = this.id;

          // Create a new <li> element that contains an <a> element.
          // Set the <a> element's text content to the video's title, and
          // add a click handler that will display Analytics data when invoked.
          var liElement = $('<li>');
          var aElement = $('<a>');
          // Setting the href value to '#' ensures that the browser renders the
          // <a> element as a clickable link.
          aElement.attr('href', '#');
          aElement.text(title);
          aElement.click(function() {
            displayVideoAnalytics(videoId);
          });

          // Call the jQuery.append() method to add the new <a> element to
          // the <li> element, and the <li> element to the parent
          // list, which is identified by the 'videoList' variable.
          liElement.append(aElement);
          videoList.append(liElement);
        });

        if (videoList.children().length == 0) {
          // Display a message if the channel does not have any viewed videos.
          displayMessage('Your channel does not have any videos that have been viewed.');
        }
      }
    });
  }

  // This function requests YouTube Analytics data for a video and displays
  // the results in a chart.
  function displayVideoAnalytics(videoId) {
    if (channelId) {
      // To use a different date range, modify the ONE_MONTH_IN_MILLISECONDS
      // variable to a different millisecond delta as desired.
      var today = new Date();
      var lastMonth = new Date(today.getTime() - ONE_MONTH_IN_MILLISECONDS);

      var request = gapi.client.youtubeAnalytics.reports.query({
        // The start-date and end-date parameters must be YYYY-MM-DD strings.
        'start-date': formatDateString(lastMonth),
        'end-date': formatDateString(today),
        // At this time, you need to explicitly specify channel==channelId.
        // See https://developers.google.com/youtube/analytics/v1/#ids
        ids: 'channel==' + channelId,
        dimensions: 'day',
        sort: 'day',
        // See https://developers.google.com/youtube/analytics/v1/available_reports
        // for details about the different filters and metrics you can request
        // if the "dimensions" parameter value is "day".
        metrics: 'views',
        filters: 'video==' + videoId
      });

      request.execute(function(response) {
        // This function is called regardless of whether the request succeeds.
        // The response contains YouTube Analytics data or an error message.
        if ('error' in response) {
          displayMessage(response.error.message);
        } else {
          displayChart(videoId, response);
        }
      });
    } else {
      // The currently authenticated user's channel ID is not available.
      displayMessage('The YouTube channel ID for the current user is not available.');
    }
  }

  // This boilerplate code takes a Date object and returns a YYYY-MM-DD string.
  function formatDateString(date) {
    var yyyy = date.getFullYear().toString();
    var mm = padToTwoCharacters(date.getMonth() + 1);
    var dd = padToTwoCharacters(date.getDate());

    return yyyy + '-' + mm + '-' + dd;
  }

  // If number is a single digit, prepend a '0'. Otherwise, return the number
  //  as a string.
  function padToTwoCharacters(number) {
    if (number < 10) {
      return '0' + number;
    } else {
      return number.toString();
    }
  }

  // Call the Google Chart Tools API to generate a chart of Analytics data.
  function displayChart(videoId, response) {
    if ('rows' in response) {
      hideMessage();

      // The columnHeaders property contains an array of objects representing
      // each column's title -- e.g.: [{name:"day"},{name:"views"}]
      // We need these column titles as a simple array, so we call jQuery.map()
      // to get each element's "name" property and create a new array that only
      // contains those values.
      var columns = $.map(response.columnHeaders, function(item) {
        return item.name;
      });
      // The google.visualization.arrayToDataTable() function wants an array
      // of arrays. The first element is an array of column titles, calculated
      // above as "columns". The remaining elements are arrays that each
      // represent a row of data. Fortunately, response.rows is already in
      // this format, so it can just be concatenated.
      // See https://developers.google.com/chart/interactive/docs/datatables_dataviews#arraytodatatable
      var chartDataArray = [columns].concat(response.rows);
      var chartDataTable = google.visualization.arrayToDataTable(chartDataArray);

      var chart = new google.visualization.LineChart(document.getElementById('chart'));
      chart.draw(chartDataTable, {
        // Additional options can be set if desired as described at:
        // https://developers.google.com/chart/interactive/docs/reference#visdraw
        title: 'Views per Day of Video ' + videoId
      });
    } else {
      displayMessage('No data available for video ' + videoId);
    }
  }

  // This helper method displays a message on the page.
  function displayMessage(message) {
    $('#message').text(message).show();
  }

  // This helper method hides a previously displayed message on the page.
  function hideMessage() {
    $('#message').hide();
  }
})();