針對資料模型「文件」中的每項文件新增中繼資料每個項目都包含檔案圖示、可在網路上開啟檔案的連結和上次更新時間。
接下來,我們需要告知 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 1.1.0 以上版本不需要經過調整,就能在嚴格 CSP 中運作。一開箱就能用!
不過,如果您使用的是 v1.0.1 和 v1.1.0 之間的舊版 Angular,就必須指示 Angular 在「內容安全模式」中執行。方法是將 ngCsp 指令與 ngApp 一起加入:
<html data-ng-app data-ng-csp>
處理授權
資料模型並非由應用程式本身產生。而是透過外部 API (Google Drive API) 填入。因此,為了填入應用程式資料,您需要進行一些工作。
我們必須針對使用者的 Google 帳戶擷取 OAuth 權杖,才能提出 API 要求。因此,我們建立了一個方法納入 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 提出要求並填入模型。
骷髏控制器
上傳者的「模型」是物件的簡易陣列 (稱為「文件」),經過算繪後
:
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 回應的資料提交文件陣列。
$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 擷取主要動態饋給。OAuth 存取權杖會以及其他自訂標頭和參數包含在 Authorization
標頭中。
successCallback
會處理 API 回應,並為動態饋給中的每個項目建立新的文件物件。
如果您現在執行 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
。
以下是含有新增 XHR 程式碼的新版 successCallback
:
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 對我們很滿意,因此我們獲得了良好的檔案圖示:
離線:快取外部資源
需要進行的明顯最佳化:不要在每次呼叫 fetchDocs()
時,為每個檔案圖示發出 100 秒的 XHR 要求。在開發人員工具控制台中,按下「重新整理」按鈕數次以進行驗證。每次都會擷取 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())。主控台會驗證此行為:
下次執行時 (或按下「Refresh」按鈕) 時,傳遞至 webkitResolveLocalFileSystemURL()
的網址就會存在,因為檔案先前已快取過。應用程式會將 doc.icon
設為檔案的 filesystem: URL
,並避免為圖示產生昂貴的 XHR。
拖曳上傳
如果無法上傳檔案,上傳程式應用程式將顯示不實廣告!
app.js 會根據名為 DnDFileController
的 HTML5 拖曳功能實作小型程式庫,藉此處理這項功能。從桌面拖曳檔案並上傳到 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;
});