สำหรับทุกเอกสารในโมเดลข้อมูลของเรา "เอกสาร" แต่ละรายการ
จะมีไอคอนไฟล์ ลิงก์สำหรับเปิดไฟล์บนเว็บ และ last updatedDate
หมายเหตุ: เราใช้แอตทริบิวต์ data-*
สำหรับ Angular เพื่อให้เทมเพลต HTML ถูกต้อง
ตัวทำซ้ำngRepeat แต่คุณไม่จำเป็นต้องทำ คุณสามารถเขียนเครื่องอ่านซ้ำได้ง่ายๆ เป็น
<li ng-repeat="doc in docs">
ต่อไป เราต้องแจ้ง 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>
โปรดทราบว่าสิ่งที่คุณไม่เห็นที่นี่คือเรากำลังเชื่อมต่อ Listener เหตุการณ์หรือพร็อพเพอร์ตี้สำหรับข้อมูล
การผูก Angular ทำงานหนักเพื่อเรา
ขั้นตอนสุดท้ายคือการทำให้ Angular ทำให้เทมเพลตของเราสว่างขึ้น วิธีทั่วไปที่ทำได้คือ
ngApp เล็กสุดใน
<html data-ng-app="gDriveApp">
นอกจากนี้คุณยังสามารถกำหนดขอบเขตแอปโดยให้มีขนาดเล็กลงของหน้าเว็บได้หากต้องการ เรามีเพียง
ตัวควบคุม 1 รายการในแอปนี้ แต่ถ้าเราจะเพิ่มตัวควบคุมอีกในภายหลัง ให้วาง 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>
แนวทางนโยบายรักษาความปลอดภัยเนื้อหา
Angular v1.1.0+ ไม่จำเป็นต้องใช้การปรับแต่งใดๆ เพื่อทำงานที่เข้มงวด ซึ่งต่างจากเฟรมเวิร์ก JS MVC อื่นๆ
CSP ใช้งานได้เลยทันที
อย่างไรก็ตาม หากคุณใช้ Angular เวอร์ชันเก่าระหว่าง v1.0.1 และ v1.1.0 คุณจะต้องแจ้ง
มุมที่จะทำงานใน "โหมดรักษาความปลอดภัยเนื้อหา" ซึ่งทำได้โดยการรวมคำสั่ง ngCsp
ข้าง ngApp
<html data-ng-app data-ng-csp>
การจัดการการให้สิทธิ์
แอปไม่ได้สร้างโมเดลข้อมูลขึ้นมา แต่สร้างขึ้นจาก API ภายนอก (หรือ
Google Drive API) ดังนั้น จึงมีงานบางอย่างที่จำเป็นต่อการป้อนข้อมูลของแอป
ก่อนที่จะส่งคำขอ API เราต้องเรียกโทเค็น OAuth สำหรับบัญชี Google ของผู้ใช้
สำหรับการดำเนินการดังกล่าว เราได้สร้างวิธีการรวมการโทรไปยัง 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 );
}
};
หมายเหตุ: การส่งผ่าน Callback ที่ไม่บังคับจะช่วยให้เราทราบว่าโทเค็น OAuth มีลักษณะดังนี้
พร้อมแล้ว
หมายเหตุ: เราได้สร้างไลบรารี gdocs.js เพื่อจัดการงานด้าน API เพื่อลดความซับซ้อนของสิ่งต่างๆ
เมื่อเราได้รับโทเค็นแล้ว ก็ถึงเวลาส่งคำขอกับ Drive API และป้อนข้อมูลโมเดล
ตัวควบคุมโครงกระดูก
"โมเดล" สำหรับตัวอัปโหลดเป็นอาร์เรย์แบบง่าย (เรียกว่าเอกสาร) ของออบเจ็กต์ที่จะแสดงผลเป็น
เหล่านั้น
s ในเทมเพลต:
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()
ใช้บริการ $http
ของ Angular เพื่อเรียกข้อมูลฟีดหลักผ่าน XHR การเข้าถึง OAuth
โทเค็นจะรวมอยู่ในส่วนหัว Authorization
พร้อมด้วยส่วนหัวและพารามิเตอร์ที่กำหนดเองอื่นๆ
successCallback
จะประมวลผลการตอบกลับของ API และสร้างออบเจ็กต์เอกสารใหม่สำหรับแต่ละรายการใน
ฟีด
หากคุณเรียกใช้ fetchDocs()
ตอนนี้ ทุกอย่างจะทำงานได้และรายการไฟล์จะแสดงขึ้น
ไชโย!
เดี๋ยวนะ...เราพลาดไอคอนไฟล์สวยๆ เหล่านั้น What gives? การตรวจสอบคอนโซลอย่างรวดเร็วแสดงให้เห็นข้อมูลมากมาย
ของข้อผิดพลาดเกี่ยวกับ CSP:
เนื่องจากเราพยายามตั้งค่าไอคอน img.src
เป็น URL ภายนอก เนื้อหานี้ละเมิด CSP สำหรับ
ตัวอย่าง: https://ssl.gstatic.com/docs/doclist/images/icon_10_document_list.png
เพื่อแก้ไขปัญหานี้
จำเป็นต้องดึงข้อมูลเนื้อหาระยะไกลเหล่านี้ลงในแอป
การนำเข้าชิ้นงานรูปภาพระยะไกล
เราใช้ XHR2 เพื่อ "นำเข้า" เพื่อให้ CSP หยุดตะโกนใส่เรา ไอคอนไฟล์เป็น 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 100 วินาทีสำหรับไอคอนไฟล์แต่ละไอคอน
การโทรหา 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 );
};
โปรดสังเกตว่าใน Callback webkitResolveLocalFileSystemURL()
เรากําลังโทรหา $scope.$apply()
เมื่อ
เห็นข้อความสุดท้าย โดยปกติจะไม่จำเป็นต้องโทรหา $apply()
Angular ตรวจพบการเปลี่ยนแปลงของข้อมูล
โมเดลโดยอัตโนมัติ อย่างไรก็ตาม ในกรณีของเรา เรามีเลเยอร์เพิ่มเติมของ Callback แบบอะซิงโครนัสที่
ไม่พบ Angular เราต้องแจ้งให้ Angular ทราบอย่างชัดเจนเมื่อโมเดลของเราได้รับการอัปเดต
เมื่อเรียกใช้ครั้งแรก ไอคอนจะไม่อยู่ในระบบไฟล์ HTML5 และมีการเรียกไปยัง
window.webkitResolveLocalFileSystemURL()
จะทำให้ระบบเรียกใช้ Callback ที่มีข้อผิดพลาด สำหรับกรณีนี้
เราก็สามารถนำเทคนิคเดิมมาใช้ใหม่
และดึงข้อมูลรูปภาพได้ ความแตกต่างเพียงอย่างเดียวในครั้งนี้คือ
ที่มีการเขียน BLOB ลงในระบบไฟล์ (ดู writeFile() ) คอนโซลจะยืนยันข้อมูลนี้
พฤติกรรม:
เมื่อเรียกใช้งานครั้งถัดไป (หรือกดปุ่ม "รีเฟรช") URL ที่ส่งไปยัง
มี 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 ;
});