Angular adalah framework MVC, jadi kita perlu menentukan aplikasi sedemikian rupa sehingga model, tampilan, dan pengontrol secara logis keluar darinya. Untungnya, hal ini sepele saat menggunakan Angular.
{i>View<i} adalah yang paling mudah, jadi mari kita mulai dari sana.
Pada akhirnya kita ingin
menampilkan daftar file pengguna. Untuk melakukannya, model
untuk setiap dokumen dalam model data "dokumen". Setiap item
berisi ikon file, link untuk membuka file di web, dan last updatedDate.
Catatan: Untuk membuat template menjadi HTML yang valid, kita menggunakan atribut data-*
untuk iterator ngRepeat Angular, tetapi Anda tidak harus menggunakannya. Anda dapat dengan mudah menulis pengulang sebagai
<li ng-repeat="doc in docs">
.
Selanjutnya, kita perlu memberi tahu Angular mana pengontrol yang akan mengawasi rendering template ini. Untuk itu, kita
menggunakan perintah ngController untuk memberi tahu DocsController
agar mengambil alih template
:
<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>
Perlu diingat, yang tidak Anda lihat di sini adalah kami menghubungkan pemroses peristiwa atau properti untuk data binding. Angular melakukan pekerjaan berat itu untuk kita!
Langkah terakhir adalah membuat Angular menghidupkan template kita. Cara umum untuk melakukannya adalah menyertakan perintah ngApp di seluruh :
<html data-ng-app="gDriveApp">
Anda juga dapat mengatur cakupan aplikasi ke bagian yang lebih kecil dari halaman jika menginginkannya. Kita hanya memiliki satu pengontrol di aplikasi ini, tetapi jika kita menambahkan lebih banyak pengontrol di lain waktu, menempatkan ngApp di elemen paling atas akan membuat seluruh halaman siap untuk Angular.
Produk akhir untuk main.html
akan terlihat seperti ini:
<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>
Sepatah kata tentang Kebijakan Keamanan Konten
Tidak seperti framework MVC JS lainnya, Angular v1.1.0+ tidak memerlukan penyesuaian agar dapat berfungsi dalam CSP yang ketat. Aplikasi ini langsung berfungsi, siap digunakan!
Namun, jika menggunakan Angular versi lama antara v1.0.1 dan v1.1.0, Anda harus memberi tahu Angular agar berjalan dalam "mode keamanan konten". Hal ini dilakukan dengan menyertakan perintah ngCsp bersama ngApp :
<html data-ng-app data-ng-csp>
Menangani otorisasi
Model data tidak dibuat oleh aplikasi itu sendiri. Sebagai gantinya, dimensi ini diisi dari API eksternal (Google Drive API). Oleh karena itu, ada sedikit tugas yang diperlukan untuk mengisi data aplikasi.
Agar dapat membuat permintaan API, kita perlu mengambil token OAuth untuk Akun Google pengguna.
Untuk itu, kita telah membuat metode untuk menggabungkan panggilan ke chrome.identity.getAuthToken()
dan menyimpan accessToken
, yang dapat kita gunakan kembali untuk panggilan mendatang ke 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);
}
};
Catatan: Meneruskan callback opsional akan memberi kami fleksibilitas untuk mengetahui kapan token OAuth sudah siap.
Catatan: Untuk sedikit menyederhanakan, kami telah membuat library gdocs.js untuk menangani tugas API.
Setelah memiliki token, saatnya membuat permintaan terhadap Drive API dan mengisi model.
Pengontrol kerangka
"Model" untuk Uploader adalah array sederhana (disebut dokumen) objek yang akan dirender
sebagai
s di {i>template<i}:
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();
});
}
Perhatikan bahwa gdocs.auth()
dipanggil sebagai bagian dari konstruktor DocsController. Saat internal Angular
membuat pengontrol, kami dipastikan memiliki token OAuth baru yang menunggu pengguna.
Mengambil data
Template ditata. Scaffolding pengontrol. token OAuth yang tersedia. Apa langkah selanjutnya?
Saatnya menentukan metode pengontrol utama, fetchDocs()
. Ini adalah pekerja keras pengontrol,
yang bertanggung jawab untuk meminta file pengguna dan mengisi array dokumen dengan data dari respons 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()
menggunakan layanan $http
Angular untuk mengambil feed utama melalui XHR. Token akses oauth disertakan dalam header Authorization
bersama dengan header dan parameter kustom lainnya.
successCallback
memproses respons API dan membuat objek dokumen baru untuk setiap entri dalam feed.
Jika Anda menjalankan fetchDocs()
sekarang, semuanya berfungsi dan daftar file akan muncul:
Wow!
Tunggu,...tidak ada ikon file yang rapi. What gives? Pemeriksaan cepat pada konsol menunjukkan sekumpulan
error terkait CSP:
Alasannya adalah kami mencoba menetapkan ikon img.src
ke URL eksternal. Hal ini melanggar CSP. Contoh: https://ssl.gstatic.com/docs/doclist/images/icon_10_document_list.png
. Untuk memperbaikinya, kita
perlu menarik aset jarak jauh ini secara lokal ke aplikasi.
Mengimpor aset gambar jarak jauh
Agar CSP berhenti meneriaki kami, kami menggunakan XHR2 untuk "mengimpor" ikon file sebagai Blob, lalu menyetel
img.src
ke blob: URL
yang dibuat oleh aplikasi.
Berikut adalah successCallback
yang telah diperbarui dengan kode XHR yang ditambahkan:
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);
}
});
});
};
Sekarang setelah CSP senang lagi dengan kita, kita mendapatkan ikon file yang bagus:
Beralih ke offline: meng-cache resource eksternal
Pengoptimalan yang jelas yang perlu dilakukan: tidak membuat 100 permintaan XHR untuk setiap ikon file di
setiap panggilan ke fetchDocs()
. Verifikasi hal ini di konsol Developer Tools dengan menekan tombol "Refresh"
beberapa kali. Setiap kali, n gambar diambil:
Mari kita ubah successCallback
untuk menambahkan lapisan cache. Penambahan tersebut ditandai dengan huruf tebal:
$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);
};
Perhatikan bahwa dalam callback webkitResolveLocalFileSystemURL()
, kita memanggil $scope.$apply()
saat
entri terakhir terlihat. Biasanya memanggil $apply()
tidak diperlukan. Angular mendeteksi perubahan pada model data secara otomatis. Namun, dalam kasus ini, kita memiliki lapisan tambahan callback asinkron yang tidak diketahui Angular. Kita harus memberi tahu Angular secara eksplisit saat model telah diperbarui.
Saat pertama kali dijalankan, ikon tidak akan berada dalam Sistem File HTML5 dan panggilan ke window.webkitResolveLocalFileSystemURL()
akan menyebabkan callback errornya dipanggil. Untuk
kasus tersebut, kita dapat menggunakan kembali teknik dari sebelumnya dan mengambil gambar. Satu-satunya perbedaan kali ini adalah
setiap blob ditulis ke sistem file (lihat writeFile() ). Konsol akan memverifikasi perilaku
ini:
Setelah dijalankan berikutnya (atau menekan tombol "Refresh"), URL yang diteruskan ke
webkitResolveLocalFileSystemURL()
ada karena file telah di-cache sebelumnya. Aplikasi menetapkan
doc.icon
ke filesystem: URL
file dan menghindari pembuatan XHR yang mahal untuk ikon tersebut.
Mengupload dengan tarik lalu lepas
Aplikasi uploader adalah iklan palsu jika tidak dapat mengupload file.
app.js menangani fitur ini dengan menerapkan library kecil di sekitar Tarik lalu Lepas HTML5 yang disebut
DnDFileController
. Hal ini memberikan kemampuan untuk menarik file dari desktop dan menguploadnya
ke Google Drive.
Cukup dengan menambahkan ini ke layanan gdocs akan melakukan tugas:
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;
});