文件。每個項目
包含檔案圖示、開啟檔案在網頁上的連結,以及上次更新日期。
接下來,我們要告知 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。一開箱就能用!
不過,如果您使用的是 1.0.1 和 1.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()
是 DocsController 建構函式中呼叫的一部分。當 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()
。在開發人員工具控制台中按下 [重新整理] 即可確認這一點
數次。每次擷取 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;
});