数据模型“docs”中的每个文档。各项
包含一个文件图标、用于在网络上打开文件的链接以及 last updatedDate。
接下来,我们需要告知 Angular 哪个控制器将监督此模板的渲染。为此,我们
使用 ngController 指令指示 DocsController 掌控模板
:
<body data-ng-controller="DocsController">
<section id="main">
  <ul>
    <li data-ng-repeat="doc in docs">
      <img data-ng-src=""> <a href=""></a> 
      <span class="date"></span>
    </li>
  </ul>
</section>
</body>
请注意,这里没有为数据连接事件监听器或属性
绑定。Angular 能为我们完成这方面的繁重工作!
最后一步是让 Angular 点亮我们的模板。实现此目的的常见方法是添加
ngApp 指令的完整路径:
<html data-ng-app="gDriveApp">
如果需要,您还可以将应用范围缩小到网页的较小部分。我们只有
一个控制器,但如果稍后添加更多控制器,只需将 ngApp 置于最顶端
元素可让整个页面为 Angular 做好准备。
main.html 的最终产品如下所示:
<html data-ng-app="gDriveApp">
<head>
  …
  <base target="_blank">
</head>
<body data-ng-controller="DocsController">
<section id="main">
  <nav>
    <h2>Google Drive Uploader</h2>
    <button class="btn" data-ng-click="fetchDocs()">Refresh</button>
    <button class="btn" id="close-button" title="Close"></button>
  </nav>
  <ul>
    <li data-ng-repeat="doc in docs">
      <img data-ng-src=""> <a href=""></a>  
      <span class="date"></span>
    </li>
  </ul>
</section>
内容安全政策简述
与许多其他 JS MVC 框架不同,Angular v1.1.0+ 无需任何调整即可在严格的
CSP。开箱即可使用!
但是,如果您使用的是版本在 v1.0.1 到 v1.1.0 之间的旧版 Angular,则需要
要在“内容安全模式”下运行的 Angular。为此,可以加入 ngCsp 指令
和 ngApp 搭配使用:
<html data-ng-app data-ng-csp>
处理授权
数据模型不是由应用本身生成的,而是通过外部 API(即
Google Drive API)。因此,填充应用数据需要进行一些工作。
在发出 API 请求之前,我们需要获取用户的 Google 账号的 OAuth 令牌。
为此,我们创建了一个方法,用于封装对 chrome.identity.getAuthToken() 的调用并存储
accessToken,我们可以在将来调用 Drive API 时重复使用它。
GDocs.prototype.auth = function(opt_callback) {
  try {
    chrome.identity.getAuthToken({interactive: false}, function(token) {
      if (token) {
        this.accessToken = token;
        opt_callback && opt_callback();
      }
    }.bind(this));
  } catch(e) {
    console.log(e);
  }
};
获得令牌后,就可以向 Drive API 发出请求并填充模型了。
骷髅控制器
“模型”是一个简单的对象数组(称为 docs),这些对象将显示为
这些
:
var gDriveApp = angular.module('gDriveApp', []);
gDriveApp.factory('gdocs', function() {
  var gdocs = new GDocs();
  return gdocs;
});
function DocsController($scope, $http, gdocs) {
  $scope.docs = [];
  $scope.fetchDocs = function() {
     ...
  };
  // Invoke on ctor call. Fetch docs after we have the oauth token.
  gdocs.auth(function() {
    $scope.fetchDocs();
  });
}
请注意,gdocs.auth() 是作为 DocumentController 构造函数的一部分进行调用的。Angular 的
内部创建控制器,因此我们保证有一个新的 OAuth 令牌等待用户。
正在提取数据
模板已布局。控制器支架式。OAuth 令牌。接下来该怎么做呢?
现在可以定义主控制器方法 fetchDocs() 了。它是控制器的主力
负责请求用户的文件,并使用来自 API 响应的数据提交 docs 数组。
$scope.fetchDocs = function() {
  $scope.docs = []; // First, clear out any old results
  // Response handler that doesn't cache file icons.
  var successCallback = function(resp, status, headers, config) {
    var docs = [];
    var totalEntries = resp.feed.entry.length;
    resp.feed.entry.forEach(function(entry, i) {
      var doc = {
        title: entry.title.$t,
        updatedDate: Util.formatDate(entry.updated.$t),
        updatedDateFull: entry.updated.$t,
        icon: gdocs.getLink(entry.link,
                            'http://schemas.google.com/docs/2007#icon').href,
        alternateLink: gdocs.getLink(entry.link, 'alternate').href,
        size: entry.docs$size ? '( ' + entry.docs$size.$t + ' bytes)' : null
      };
      $scope.docs.push(doc);
      // Only sort when last entry is seen.
      if (totalEntries - 1 == i) {
        $scope.docs.sort(Util.sortByDate);
      }
    });
  };
  var config = {
    params: {'alt': 'json'},
    headers: {
      'Authorization': 'Bearer ' + gdocs.accessToken,
      'GData-Version': '3.0'
    }
  };
  $http.get(gdocs.DOCLIST_FEED, config).success(successCallback);
};
fetchDocs() 使用 Angular 的 $http 服务通过 XHR 检索主 Feed。OAuth 访问
令牌与其他自定义标头和参数一起包含在 Authorization 标头中。
successCallback 处理 API 响应,并为
Feed。
如果您现在运行 fetchDocs(),一切正常,屏幕上会显示文件列表:

耶!
等等,我们找不到那些干净利落的文件图标了。What gives? 快速查看一下控制台,您会发现许多
与 CSP 相关的错误:

原因是我们要尝试将图标 img.src 设置为外部网址。这违反了 CSP。例如:https://ssl.gstatic.com/docs/doclist/images/icon_10_document_list.png。为了解决这个问题
您需要将这些远程资源本地提取到应用
导入远程图片素材资源
为了让 CSP 停止对我们大喊大叫,我们使用 XHR2 来“导入”将文件图标设置为 Blob,然后将
img.src 到应用创建的 blob: URL。
以下是更新后的 successCallback,其中包含添加的 XHR 代码:
var successCallback = function(resp, status, headers, config) {
  var docs = [];
  var totalEntries = resp.feed.entry.length;
  resp.feed.entry.forEach(function(entry, i) {
    var doc = {
      ...
    };
    $http.get(doc.icon, {responseType: 'blob'}).success(function(blob) {
      console.log('Fetched icon via XHR');
      blob.name = doc.iconFilename; // Add icon filename to blob.
      writeFile(blob); // Write is async, but that's ok.
      doc.icon = window.URL.createObjectURL(blob);
      $scope.docs.push(doc);
      // Only sort when last entry is seen.
      if (totalEntries - 1 == i) {
        $scope.docs.sort(Util.sortByDate);
      }
    });
  });
};
我们现在又对 CSP 很满意,因此我们为您提供了实用的文件图标:

离线:缓存外部资源
需要进行的显而易见的优化:不要对上的每个文件图标发出数百个 XHR 请求
对 fetchDocs() 的每次调用。请在开发者工具控制台中按“刷新”按钮进行验证
按钮多次播放。每次提取 n 张图片:

我们来修改 successCallback 以添加缓存层。新增以粗体突出显示:
$scope.fetchDocs = function() {
  ...
  // Response handler that caches file icons in the filesystem API.
  var successCallbackWithFsCaching = function(resp, status, headers, config) {
    var docs = [];
    var totalEntries = resp.feed.entry.length;
    resp.feed.entry.forEach(function(entry, i) {
      var doc = {
        ...
      };
      // 'https://ssl.gstatic.com/doc_icon_128.png' -> 'doc_icon_128.png'
      doc.iconFilename = doc.icon.substring(doc.icon.lastIndexOf('/') + 1);
      // If file exists, it we'll get back a FileEntry for the filesystem URL.
      // Otherwise, the error callback will fire and we need to XHR it in and
      // write it to the FS.
      var fsURL = fs.root.toURL() + FOLDERNAME + '/' + doc.iconFilename;
      window.webkitResolveLocalFileSystemURL(fsURL, function(entry) {
        doc.icon = entry.toURL(); // should be === to fsURL, but whatevs.
        $scope.docs.push(doc); // add doc to model.
        // Only want to sort and call $apply() when we have all entries.
        if (totalEntries - 1 == i) {
          $scope.docs.sort(Util.sortByDate);
          $scope.$apply(function($scope) {}); // Inform angular that we made changes.
        }
      }, function(e) {
        // Error: file doesn't exist yet. XHR it in and write it to the FS.
        $http.get(doc.icon, {responseType: 'blob'}).success(function(blob) {
          console.log('Fetched icon via XHR');
          blob.name = doc.iconFilename; // Add icon filename to blob.
          writeFile(blob); // Write is async, but that's ok.
          doc.icon = window.URL.createObjectURL(blob);
          $scope.docs.push(doc);
          // Only sort when last entry is seen.
          if (totalEntries - 1 == i) {
            $scope.docs.sort(Util.sortByDate);
          }
        });
      });
    });
  };
  var config = {
    ...
  };
  $http.get(gdocs.DOCLIST_FEED, config).success(successCallbackWithFsCaching);
};
请注意,在 webkitResolveLocalFileSystemURL() 回调中,当出现以下情况时,我们将调用 $scope.$apply():
只能看到最后一个条目。通常不需要调用 $apply()。Angular 检测数据更改
模型。不过,在本例中,我们还额外添加了一个异步回调层,
Angular 则不知情。当模型更新时,我们必须明确告知 Angular。
首次运行时,这些图标不会出现在 HTML5 文件系统中,且对
window.webkitResolveLocalFileSystemURL() 将导致调用其错误回调。为此
我们可以重复使用之前的技术并提取图片。这次唯一的区别是
确保每个 blob 都会写入文件系统(请参阅 writeFile())。控制台会验证
行为:

下次运行(或按下“刷新”按钮)时,传递至
“webkitResolveLocalFileSystemURL()”已存在,因为该文件之前已缓存。该应用将
将 doc.icon 添加到文件的 filesystem: URL,并避免为图标生成代价高昂的 XHR。
拖放上传
如果上传程序应用无法上传文件,就是虚假广告!
app.js 通过实现一个围绕 HTML5 拖放的小型库(称为
DnDFileController。它支持从桌面拖入文件并进行上传
保存到 Google 云端硬盘。
只需将以下代码添加到 gdocs 服务即可:
gDriveApp.factory('gdocs', function() {
  var gdocs = new GDocs();
  var dnd = new DnDFileController('body', function(files) {
    var $scope = angular.element(this).scope();
    Util.toArray(files).forEach(function(file, i) {
      gdocs.upload(file, function() {
        $scope.fetchDocs();
      });
    });
  });
  return gdocs;
});