لكل مستند في نموذج "مستندات" نموذج البيانات لدينا. كل عنصر
يحتوي على رمز ملف ورابط لفتحه على الويب وآخر updateDate.
ملاحظة: لجعل النموذج صالحًا بتنسيق HTML، نستخدم سمات data-*
للغة Angular
يمكن تكرار 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>
ما لا يظهر لك هنا هو ربط أدوات معالجة الأحداث أو المواقع الإلكترونية بالبيانات
الربط. يُجري 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 الأخرى، لا يتطلب الإصدار 1.1.0 أو الإصدارات الأحدث من Angular أي تعديلات للعمل ضمن نطاق
سياسة CSP : إنها طريقة سهلة الإعداد،
ومع ذلك، إذا كنت تستخدم إصدارًا قديمًا من Angular بين v1.0.1 وv1.1.0، يجب معرفة ذلك.
Angular للتشغيل في "وضع أمان المحتوى". ويتم ذلك من خلال تضمين التوجيه ngCsp .
إلى جانب ngApp :
<html data-ng-app data-ng-csp>
التعامل مع التفويض
لا يتم إنشاء نموذج البيانات بواسطة التطبيق نفسه. وإنما تتم تعبئته من خلال واجهة برمجة تطبيقات خارجية (
Google Drive 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 );
}
};
ملاحظة: يتيح لنا تمرير معاودة الاتصال الاختيارية معرفة متى يصبح رمز OAuth المميز مرنًا.
ملاحظة: لتبسيط الأمور قليلاً، أنشأنا مكتبة gdocs.js للتعامل مع مهام واجهة برمجة التطبيقات.
بعد حصولنا على الرمز المميّز، يحين وقت تقديم طلبات مقابل واجهة برمجة تطبيقات Drive وملء النموذج.
وحدة تحكّم هيكلية
"النموذج" للقائم بالتحميل، هي مصفوفة بسيطة (تسمى مستندات) من الكائنات التي سيتم عرضها على هيئة
تلك
في القالب:
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()
. إنه ركيزة وحدة التحكم،
مسؤولة عن طلب ملفات المستخدم وملء مصفوفة المستندات ببيانات من ردود واجهة برمجة التطبيقات.
$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
استجابة واجهة برمجة التطبيقات وتنشئ كائن مستند جديدًا لكل إدخال في
الخلاصة.
إذا شغّلت fetchDocs()
الآن، ستسير الأمور على ما يرام وستظهر قائمة الملفات:
رائع.
مهلاً،...تنقصنا رموز الملفات الرائعة هذه. What gives? يُظهر التحقق السريع من وحدة التحكم مجموعة
من الأخطاء المتعلقة بسياسة CSP:
السبب هو أننا نحاول ضبط الرموز img.src
على عناوين URL خارجية. يخالف هذا المحتوى سياسة CSP. بالنسبة
مثال: https://ssl.gstatic.com/docs/doclist/images/icon_10_document_list.png
لإصلاح ذلك،
لسحب الأصول البعيدة هذه محليًا إلى التطبيق.
جارٍ استيراد مواد عرض الصور عن بُعد
لكي يتوقف CSP عن الصراخ علينا، نستخدم XHR2 من أجل "الاستيراد" أيقونات الملف على أنها Blobs، ثم قم بتعيين
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 سعيدة معنا مرة أخرى، حصلنا على رموز ملفات رائعة:
العمل بلا اتصال بالإنترنت: تخزين الموارد الخارجية مؤقتًا
التحسين الواضح الذي يجب إجراؤه: عدم إجراء 100 ثانية من طلبات XHR لكل رمز ملف على
كل مكالمة إلى 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() ). تتحقّق وحدة التحكّم من هذا الإجراء.
السلوك:
عند التشغيل التالي (أو الضغط على الزر "تحديث")، تم تمرير عنوان URL إلى
يتوفّر webkitResolveLocalFileSystemURL()
لأنه سبق أن تم تخزين الملف مؤقتًا. يحدد التطبيق
doc.icon
إلى filesystem: URL
في الملف وتجنُّب إجراء XHR المكلف للرمز.
التحميل من خلال سحبه وإفلاته
يُعد تطبيق القائم بالتحميل إعلانًا خاطئًا إذا لم يتمكن من تحميل ملفات!
تعالج علامة تبويب app.js هذه الميزة من خلال تنفيذ مكتبة صغيرة حول "السحب والإفلات" في HTML5 والتي تسمى
DnDFileController
يتيح إمكانية سحب الملفات من سطح المكتب وتحميلها
إلى Google Drive.
تؤدي إضافة هذا إلى خدمة 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 ;
});