এই নির্দেশিকাটি আপনাকে AngularJS MVC ফ্রেমওয়ার্কের সাথে Chrome Apps তৈরি করা শুরু করে। অ্যাঙ্গুলারকে অ্যাকশনে চিত্রিত করার জন্য, আমরা ফ্রেমওয়ার্ক ব্যবহার করে নির্মিত একটি প্রকৃত অ্যাপ উল্লেখ করব, Google ড্রাইভ আপলোডার৷ সোর্স কোড GitHub এ উপলব্ধ।
অ্যাপ সম্পর্কে
Google ড্রাইভ আপলোডার ব্যবহারকারীদের দ্রুত দেখতে এবং তাদের Google ড্রাইভ অ্যাকাউন্টে সঞ্চিত ফাইলগুলির সাথে ইন্টারঅ্যাক্ট করার পাশাপাশি HTML ড্র্যাগ এবং ড্রপ API ব্যবহার করে নতুন ফাইল আপলোড করার অনুমতি দেয়৷ এটি এমন একটি অ্যাপ তৈরির একটি দুর্দান্ত উদাহরণ যা Google এর APIগুলির একটির সাথে কথা বলে; এই ক্ষেত্রে, Google Drive API.
আপলোডার ব্যবহারকারীর ডেটা অ্যাক্সেস করতে OAuth2 ব্যবহার করে। chrome.identity API লগ-ইন করা ব্যবহারকারীর জন্য একটি OAuth টোকেন আনার ব্যবস্থা করে, তাই আমাদের জন্য কঠোর পরিশ্রম করা হয়েছে! একবার আমাদের কাছে দীর্ঘস্থায়ী অ্যাক্সেস টোকেন হয়ে গেলে, অ্যাপগুলি ব্যবহারকারীর ডেটা অ্যাক্সেস করতে Google ড্রাইভ API ব্যবহার করে।
এই অ্যাপটি ব্যবহার করে মূল বৈশিষ্ট্যগুলি:
- CSP- এর জন্য AngularJS-এর স্বয়ংক্রিয় শনাক্তকরণ
- Google Drive API থেকে আনা ফাইলগুলির একটি তালিকা রেন্ডার করুন৷
- HTML5 ফাইলসিস্টেম API ফাইল আইকনগুলি অফলাইনে সংরক্ষণ করতে
- HTML5 ডেস্কটপ থেকে নতুন ফাইল আমদানি/আপলোড করার জন্য টেনে আনুন
- XHR2 ছবি লোড করতে, ক্রস-ডোমেন
- OAuth অনুমোদনের জন্য chrome.identity API
- অ্যাপের নিজস্ব ন্যাভিবার চেহারা এবং অনুভূতি সংজ্ঞায়িত করার জন্য ক্রোমহীন ফ্রেম
ম্যানিফেস্ট তৈরি করা
সমস্ত Chrome অ্যাপ্লিকেশানগুলির জন্য একটি manifest.json
ফাইলের প্রয়োজন যাতে Chrome-এর অ্যাপটি চালু করার জন্য প্রয়োজনীয় তথ্য থাকে৷ ম্যানিফেস্টে প্রাসঙ্গিক মেটাডেটা থাকে এবং অ্যাপটি চালানোর জন্য প্রয়োজন এমন কোনো বিশেষ অনুমতি তালিকাভুক্ত করে।
আপলোডারের ম্যানিফেস্টের একটি স্ট্রাইপ ডাউন সংস্করণ দেখতে এইরকম:
{
"name": "Google Drive Uploader",
"version": "0.0.1",
"manifest_version": 2,
"oauth2": {
"client_id": "665859454684.apps.googleusercontent.com",
"scopes": [
"https://www.googleapis.com/auth/drive"
]
},
...
"permissions": [
"https://docs.google.com/feeds/",
"https://docs.googleusercontent.com/",
"https://spreadsheets.google.com/feeds/",
"https://ssl.gstatic.com/",
"https://www.googleapis.com/"
]
}
এই ম্যানিফেস্টের সবচেয়ে গুরুত্বপূর্ণ অংশ হল "oauth2" এবং "অনুমতি" বিভাগ।
"oauth2" বিভাগটি OAuth2 এর ম্যাজিক করার জন্য প্রয়োজনীয় প্যারামিটারগুলিকে সংজ্ঞায়িত করে৷ একটি "ক্লায়েন্ট_আইডি" তৈরি করতে, আপনার ক্লায়েন্ট আইডি পান এর নির্দেশাবলী অনুসরণ করুন। "স্কোপগুলি" অনুমোদনের সুযোগগুলি তালিকাভুক্ত করে যেগুলির জন্য OAuth টোকেন বৈধ হবে (উদাহরণস্বরূপ, অ্যাপটি যে APIগুলি অ্যাক্সেস করতে চায়)।
"অনুমতি" বিভাগে এমন URL রয়েছে যা অ্যাপটি XHR2 এর মাধ্যমে অ্যাক্সেস করবে। কোন ক্রস-ডোমেন অনুরোধগুলিকে অনুমতি দিতে হবে তা জানার জন্য Chrome-এর জন্য URL উপসর্গগুলির প্রয়োজন৷
ইভেন্ট পৃষ্ঠা তৈরি করা হচ্ছে
অ্যাপটি চালু করতে এবং সিস্টেম ইভেন্টগুলিতে প্রতিক্রিয়া জানাতে সমস্ত Chrome অ্যাপের একটি পটভূমি স্ক্রিপ্ট/পৃষ্ঠা প্রয়োজন৷
এর background.js স্ক্রিপ্টে, ড্রাইভ আপলোডার মূল পৃষ্ঠায় একটি 500x600px উইন্ডো খোলে। এটি উইন্ডোটির জন্য একটি ন্যূনতম উচ্চতা এবং প্রস্থও নির্দিষ্ট করে যাতে বিষয়বস্তু খুব বেশি কুঁচকে না যায়:
chrome.app.runtime.onLaunched.addListener(function(launchData) {
chrome.app.window.create('../main.html', {
id: "GDriveExample",
bounds: {
width: 500,
height: 600
},
minWidth: 500,
minHeight: 600,
frame: 'none'
});
});
উইন্ডোটি একটি ক্রোমলেস উইন্ডো হিসাবে তৈরি করা হয়েছে (ফ্রেম: 'কোনও নয়')। ডিফল্টরূপে, উইন্ডোগুলি OS-এর ডিফল্ট বন্ধ/প্রসারিত/মিনিমাইজ বার দিয়ে রেন্ডার করে:
আপলোডার frame: 'none'
উইন্ডোটিকে একটি "খালি স্লেট" হিসাবে রেন্ডার করতে এবং main.html
এ একটি কাস্টম ক্লোজ বোতাম তৈরি করে :
সমগ্র নৌচলাচল এলাকা একটি আবৃত করা হয়
<style>
nav:hover #close-button {
opacity: 1;
}
#close-button {
float: right;
padding: 0 5px 2px 5px;
font-weight: bold;
opacity: 0;
transition: all 0.3s ease-in-out;
}
</style>
<button class="btn" id="close-button" title="Close">x</button>
app.js- এ, এই বোতামটি window.close()
এর সাথে সংযুক্ত থাকে।
অ্যাপটি কৌণিক উপায়ে ডিজাইন করা
Angular হল একটি MVC ফ্রেমওয়ার্ক, তাই আমাদের অ্যাপটিকে এমনভাবে সংজ্ঞায়িত করতে হবে যাতে একটি মডেল, ভিউ এবং কন্ট্রোলার যৌক্তিকভাবে এর বাইরে পড়ে যায়। ভাগ্যক্রমে, কৌণিক ব্যবহার করার সময় এটি তুচ্ছ।
ভিউটি সবচেয়ে সহজ, তাই সেখান থেকে শুরু করা যাক।
ভিউ তৈরি করা
main.html হল MVC-তে "V"; যেখানে আমরা ডেটা রেন্ডার করার জন্য HTML টেমপ্লেটগুলিকে সংজ্ঞায়িত করি। কৌণিক ভাষায়, টেমপ্লেট হল কিছু বিশেষ সস সহ HTML এর সাধারণ ব্লক।
শেষ পর্যন্ত আমরা ব্যবহারকারীর ফাইলের তালিকা প্রদর্শন করতে চাই। যে জন্য, একটি সহজ
- তালিকা অর্থবোধ করে। কৌণিক বিটগুলি গাঢ়ভাবে হাইলাইট করা হয়েছে:
<ul>
<li data-ng-repeat="doc in docs">
<img data-ng-src=""> <a href=""></a>
<span class="date"></span>
</li>
</ul>
এটি দেখতে ঠিক যেমনটি পড়ে: স্ট্যাম্প আউট একটি
এর পরে, আমাদের কৌণিককে বলতে হবে কোন নিয়ামক এই টেমপ্লেটের রেন্ডারিং তত্ত্বাবধান করবে। এর জন্য, আমরা DocsController
টেমপ্লেটের উপর রাজত্ব করতে বলার জন্য ngController নির্দেশিকা ব্যবহার করি
<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>
মনে রাখবেন, আপনি এখানে যা দেখতে পাচ্ছেন না তা হল আমরা ইভেন্ট শ্রোতাদের বা ডেটা বাইন্ডিংয়ের জন্য বৈশিষ্ট্যগুলিকে সংযুক্ত করছি। কৌণিক আমাদের জন্য যে ভারী উত্তোলন করছে!
শেষ ধাপ হল আমাদের টেমপ্লেটগুলিকে কৌণিক আলোকিত করা। এটি করার সাধারণ উপায় হল ngApp নির্দেশিকা সব সময় পর্যন্ত অন্তর্ভুক্ত করা :
<html data-ng-app="gDriveApp">
আপনি চাইলে অ্যাপটিকে পৃষ্ঠার একটি ছোট অংশে স্কোপ করতে পারেন। এই অ্যাপে আমাদের শুধুমাত্র একটি কন্ট্রোলার আছে, কিন্তু আমরা যদি পরে আরও যোগ করি, তাহলে ngAppকে শীর্ষস্থানীয় উপাদানে রাখলে পুরো পৃষ্ঠাটি কৌণিক-প্রস্তুত হয়ে যায়।
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 ফ্রেমওয়ার্কের বিপরীতে, কৌণিক v1.1.0+ একটি কঠোর CSP-এর মধ্যে কাজ করার জন্য কোনো পরিবর্তনের প্রয়োজন নেই। এটা শুধু কাজ করে, বাক্সের বাইরে!
যাইহোক, আপনি যদি v1.0.1 এবং v1.1.0-এর মধ্যে Angular-এর একটি পুরানো সংস্করণ ব্যবহার করেন, তাহলে আপনাকে Angular কে "সামগ্রী নিরাপত্তা মোডে" চালানোর জন্য বলতে হবে। ngApp-এর পাশাপাশি ngCsp নির্দেশিকা অন্তর্ভুক্ত করে এটি করা হয়:
<html data-ng-app data-ng-csp>
হ্যান্ডলিং অনুমোদন
ডেটা মডেলটি অ্যাপ নিজেই তৈরি করে না। পরিবর্তে, এটি একটি বাহ্যিক API (Google ড্রাইভ API) থেকে জনবহুল। সুতরাং, অ্যাপের ডেটা পূরণ করার জন্য কিছু কাজ করা দরকার।
আমরা একটি API অনুরোধ করতে পারার আগে, আমাদের ব্যবহারকারীর Google অ্যাকাউন্টের জন্য একটি OAuth টোকেন আনতে হবে। এর জন্য, আমরা chrome.identity.getAuthToken()
এ কলটি মোড়ানো এবং accessToken
সংরক্ষণ করার জন্য একটি পদ্ধতি তৈরি করেছি, যা আমরা ড্রাইভ এপিআইতে ভবিষ্যতের কলগুলির জন্য পুনরায় ব্যবহার করতে পারি।
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);
}
};
একবার আমাদের কাছে টোকেন হয়ে গেলে, ড্রাইভ 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()
XHR এর উপর প্রধান ফিড পুনরুদ্ধার করতে Angular এর $http
পরিষেবা ব্যবহার করে। অন্যান্য কাস্টম শিরোনাম এবং পরামিতিগুলির সাথে Authorization
হেডারে ওউথ অ্যাক্সেস টোকেন অন্তর্ভুক্ত করা হয়েছে।
successCallback
API প্রতিক্রিয়া প্রক্রিয়া করে এবং ফিডে প্রতিটি এন্ট্রির জন্য একটি নতুন ডক অবজেক্ট তৈরি করে।
আপনি যদি এখনই fetchDocs()
চালান, সবকিছু কাজ করে এবং ফাইলগুলির তালিকা দেখায়:
উওট !
অপেক্ষা করুন,...আমরা সেই সুন্দর ফাইল আইকনগুলো মিস করছি। কি দেয়? কনসোলের একটি দ্রুত চেক সিএসপি-সম্পর্কিত ত্রুটিগুলির একটি গুচ্ছ দেখায়:
কারণ হল আমরা img.src
আইকনগুলিকে বাহ্যিক URL-এ সেট করার চেষ্টা করছি৷ এটি CSP লঙ্ঘন করে। উদাহরণস্বরূপ: https://ssl.gstatic.com/docs/doclist/images/icon_10_document_list.png
। এটি ঠিক করতে, আমাদের এই দূরবর্তী সম্পদগুলিকে স্থানীয়ভাবে অ্যাপে টানতে হবে।
দূরবর্তী চিত্র সম্পদ আমদানি করা হচ্ছে
CSP আমাদের চিৎকার করা বন্ধ করার জন্য, আমরা ব্লবস হিসাবে ফাইল আইকনগুলিকে "আমদানি" করতে XHR2 ব্যবহার করি, তারপরে 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()
এ প্রতিটি কলে প্রতিটি ফাইল আইকনের জন্য 100s XHR অনুরোধ করবেন না। "রিফ্রেশ" বোতামটি বেশ কয়েকবার টিপে বিকাশকারী সরঞ্জাম কনসোলে এটি যাচাই করুন৷ প্রতিবার, 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()
কল করার প্রয়োজন হয় না। কৌণিক স্বয়ংক্রিয়ভাবে ডেটা মডেলের পরিবর্তন সনাক্ত করে। তবে আমাদের ক্ষেত্রে, আমাদের কাছে অ্যাসিঙ্ক্রোনাস কলব্যাকের একটি অতিরিক্ত স্তর রয়েছে যা কৌণিক জানে না। আমাদের মডেল আপডেট করা হয়েছে যখন আমরা স্পষ্টভাবে কৌণিক বলতে হবে.
প্রথমবারে, আইকনগুলি HTML5 ফাইলসিস্টেমে থাকবে না এবং window.webkitResolveLocalFileSystemURL()
এ কল করার ফলে এটির ত্রুটি কলব্যাক আহ্বান করা হবে। সেই ক্ষেত্রে, আমরা আগে থেকে কৌশলটি পুনরায় ব্যবহার করতে পারি এবং চিত্রগুলি আনতে পারি। এবারের পার্থক্য হল প্রতিটি ব্লব ফাইলসিস্টেমে লেখা হয়েছে ( writFile() দেখুন)। কনসোল এই আচরণ যাচাই করে:
পরবর্তী রানের পরে (বা "রিফ্রেশ" বোতাম টিপুন), webkitResolveLocalFileSystemURL()
এ পাস করা URLটি বিদ্যমান থাকে কারণ ফাইলটি আগে ক্যাশে করা হয়েছে৷ অ্যাপটি ফাইলের ফাইল সিস্টেমে doc.icon
সেট করে filesystem: URL
এবং আইকনের জন্য ব্যয়বহুল XHR তৈরি করা এড়িয়ে যায়।
আপলোডিং টানুন এবং ছেড়ে দিন
একটি আপলোডার অ্যাপ ফাইল আপলোড করতে না পারলে মিথ্যা বিজ্ঞাপন!
app.js DnDFileController
নামক HTML5 ড্র্যাগ অ্যান্ড ড্রপের চারপাশে একটি ছোট লাইব্রেরি প্রয়োগ করে এই বৈশিষ্ট্যটি পরিচালনা করে। এটি ডেস্কটপ থেকে ফাইলগুলিকে টেনে আনতে এবং সেগুলিকে 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;
});