Angular là một khung MVC, vì vậy, chúng ta cần xác định ứng dụng sao cho mô hình, khung hiển thị và bộ điều khiển có thể sử dụng nó một cách hợp lý. Thật may là điều này không hề nhỏ khi sử dụng Angular.
Chế độ xem là dễ nhất, vì vậy hãy bắt đầu từ đó.
Cuối cùng, chúng ta cần hiển thị danh sách tệp của người dùng. Để làm được điều đó, một đơn giản
cho mọi tài liệu trong mô hình dữ liệu "tài liệu" của chúng tôi. Mỗi mục đều chứa một biểu tượng tệp, đường liên kết để mở tệp trên web và updateDate gần đây nhất.
Lưu ý: Để mẫu HTML hợp lệ, chúng tôi đang sử dụng thuộc tính data-*
cho trình lặp ngRepeat của Angular, nhưng bạn không bắt buộc phải làm vậy. Bạn có thể dễ dàng viết repeater là <li ng-repeat="doc in docs">
.
Tiếp theo, chúng ta cần cho Angular biết bộ điều khiển nào sẽ giám sát quá trình hiển thị mẫu này. Do đó, chúng tôi sử dụng lệnh ngController để yêu cầu DocsController
thống trị mẫu:
<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>
Xin lưu ý rằng bạn không thấy ở đây là chúng tôi kết nối các trình nghe sự kiện hoặc thuộc tính để liên kết dữ liệu. Angular đang làm công việc nặng nhọc cho chúng tôi!
Bước cuối cùng là làm cho Angular làm sáng các mẫu của chúng ta. Cách điển hình để thực hiện việc này là đưa vào lệnh ngApp ngay từ đầu :
<html data-ng-app="gDriveApp">
Bạn cũng có thể thu hẹp ứng dụng xuống một phần nhỏ hơn của trang nếu muốn. Chúng ta chỉ có một bộ điều khiển trong ứng dụng này, nhưng nếu sau này chúng ta bổ sung thêm, việc đặt ngApp lên phần tử trên cùng sẽ giúp toàn bộ trang sẵn sàng cho Angular.
Sản phẩm hoàn thiện cho main.html
sẽ có dạng như sau:
<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>
Lưu ý về Chính sách bảo mật nội dung
Không giống như nhiều khung JS MVC khác, Angular phiên bản 1.1.0 trở lên không yêu cầu chỉnh sửa để hoạt động trong một CSP nghiêm ngặt. Tính năng này hoạt động ngay tức thì!
Tuy nhiên, nếu đang sử dụng phiên bản Angular cũ trong khoảng từ phiên bản 1.0.1 đến phiên bản 1.1.0, bạn cần yêu cầu Angular chạy ở "chế độ bảo mật nội dung". Bạn có thể thực hiện việc này bằng cách thêm lệnh ngCsp cùng với ngApp :
<html data-ng-app data-ng-csp>
Xử lý việc uỷ quyền
Mô hình dữ liệu không do chính ứng dụng tạo. Thay vào đó, dữ liệu này được điền từ một API bên ngoài (API Google Drive). Do đó, bạn cần làm một số việc để điền dữ liệu của ứng dụng.
Trước khi có thể tạo yêu cầu API, chúng ta cần tìm nạp mã thông báo OAuth cho Tài khoản Google của người dùng.
Do đó, chúng ta đã tạo một phương thức để gói lệnh gọi đến chrome.identity.getAuthToken()
và lưu trữ accessToken
mà chúng ta có thể sử dụng lại cho các lệnh gọi sau này đến API Drive.
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);
}
};
Lưu ý: Việc truyền lệnh gọi lại không bắt buộc giúp chúng ta linh hoạt biết được thời điểm mã thông báo OAuth đã sẵn sàng.
Lưu ý: Để đơn giản hoá mọi thứ một chút, chúng tôi đã tạo một thư viện gdocs.js để xử lý các tác vụ API.
Sau khi có mã thông báo, đã đến lúc đưa ra yêu cầu đối với API Drive và đưa vào mô hình.
Bộ điều khiển bộ xương
"Mô hình" cho Trình tải lên là một mảng đơn giản (còn gọi là tài liệu) gồm các đối tượng sẽ được kết xuất dưới dạng các đối tượng đó.
trong mẫu:
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();
});
}
Lưu ý rằng gdocs.auth()
được gọi như một phần của hàm khởi tạo DocsController. Khi nội bộ của Angular tạo bộ điều khiển, chúng tôi đảm bảo sẽ có một mã thông báo OAuth mới chờ người dùng.
Đang tìm nạp dữ liệu
Đã bố trí mẫu. Bộ điều khiển đã được nâng cấp. Có mã thông báo OAuth. Bây giờ bạn phải làm gì?
Đã đến lúc xác định phương thức điều khiển chính, fetchDocs()
. Đó là công việc của bộ kiểm soát, chịu trách nhiệm yêu cầu các tệp của người dùng và gửi mảng tài liệu với dữ liệu từ các phản hồi của 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()
sử dụng dịch vụ $http
của Angular để truy xuất nguồn cấp dữ liệu chính qua XHR. Mã truy cập OAuth
có trong tiêu đề Authorization
cùng với các tiêu đề và thông số tuỳ chỉnh khác.
successCallback
xử lý phản hồi của API và tạo một đối tượng tài liệu mới cho mỗi mục nhập trong nguồn cấp dữ liệu.
Nếu bạn chạy fetchDocs()
ngay bây giờ, mọi thứ đều hoạt động và danh sách tệp sẽ xuất hiện:
Tuyệt!
Đợi đã,...chúng tôi thiếu các biểu tượng tệp gọn gàng đó. What gives? Thao tác kiểm tra nhanh bảng điều khiển sẽ cho thấy một loạt lỗi liên quan đến CSP:
Lý do là chúng tôi đang cố gắng đặt biểu tượng img.src
thành các URL bên ngoài. Điều này vi phạm Chính sách bảo mật nội dung (CSP). Ví dụ: https://ssl.gstatic.com/docs/doclist/images/icon_10_document_list.png
. Để khắc phục vấn đề này, chúng ta cần đưa các thành phần từ xa này vào ứng dụng.
Đang nhập thành phần hình ảnh từ xa
Để CSP dừng việc chửi rủa chúng ta, chúng ta sử dụng XHR2 để "nhập" các biểu tượng tệp dưới dạng Blobs, sau đó đặt
img.src
thành một blob: URL
do ứng dụng tạo.
Dưới đây là successCallback
đã cập nhật có thêm mã 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);
}
});
});
};
Giờ đây khi CSP hài lòng trở lại, chúng ta sẽ nhận được các biểu tượng tệp đẹp mắt:
Chuyển sang chế độ ngoại tuyến: lưu tài nguyên bên ngoài vào bộ nhớ đệm
Phương pháp tối ưu hoá rõ ràng cần được thực hiện: không thực hiện hàng trăm yêu cầu XHR cho mỗi biểu tượng tệp trong mỗi lệnh gọi đến fetchDocs()
. Xác minh điều này trong bảng điều khiển Công cụ cho nhà phát triển bằng cách nhấn nút "Làm mới" vài lần. Mỗi lần, n hình ảnh được tìm nạp:
Hãy sửa đổi successCallback
để thêm một lớp lưu vào bộ nhớ đệm. Các bổ sung được in đậm:
$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);
};
Xin lưu ý rằng trong lệnh gọi lại webkitResolveLocalFileSystemURL()
, chúng ta sẽ gọi $scope.$apply()
khi nhìn thấy mục nhập cuối cùng. Thông thường, bạn không cần gọi $apply()
. Angular phát hiện các thay đổi đối với mô hình dữ liệu theo cách tự động. Tuy nhiên, trong trường hợp này, chúng ta có thêm một lớp gọi lại không đồng bộ mà Angular không biết. Chúng ta phải thông báo rõ ràng cho Angular khi mô hình của chúng ta được cập nhật.
Trong lần chạy đầu tiên, các biểu tượng sẽ không có trong Hệ thống tệp HTML5 và các lệnh gọi đến window.webkitResolveLocalFileSystemURL()
sẽ dẫn đến việc gọi lại lỗi. Trong trường hợp đó, chúng ta có thể sử dụng lại kỹ thuật trước đó và tìm nạp hình ảnh. Điểm khác biệt duy nhất lần này là mỗi blob được ghi vào hệ thống tệp (xem writeFile() ). Bảng điều khiển sẽ xác minh hành vi này:
Trong lần chạy tiếp theo (hoặc nhấn nút "Làm mới"), URL được truyền đến webkitResolveLocalFileSystemURL()
tồn tại vì tệp này đã được lưu vào bộ nhớ đệm trước đó. Ứng dụng sẽ đặt doc.icon
thành filesystem: URL
của tệp và tránh tạo XHR tốn kém cho biểu tượng.
Tải lên bằng cách kéo và thả
Ứng dụng tải lên đang quảng cáo sai sự thật nếu ứng dụng này không thể tải tệp lên!
app.js xử lý tính năng này bằng cách triển khai một thư viện nhỏ xoay quanh tính năng Kéo và thả HTML5 có tên là
DnDFileController
. Ứng dụng này cho phép bạn kéo các tệp từ máy tính để bàn và tải chúng lên Google Drive.
Bạn chỉ cần thêm đoạn mã này vào dịch vụ Google Tài liệu để thực hiện công việc:
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;
});