في هذه الخطوة، ستتعرف على:
- كيفية تكييف تطبيق ويب حالي مع النظام الأساسي لتطبيقات Chrome.
- كيفية التأكّد من التزام النصوص البرمجية لتطبيقك بسياسة أمان المحتوى (CSP)
- كيفية تنفيذ التخزين المحلي باستخدام chrome.storage.local.
الوقت المقدّر لإكمال هذه الخطوة: 20 دقيقة.
لمعاينة ما ستُكمله في هذه الخطوة، انتقِل إلى أسفل هذه الصفحة ↓.
استيراد تطبيق Todo حالي
كنقطة انطلاق، عليك استيراد إصدار JavaScript من TodoMVC، وهو مقياس أداء مشترك. تطبيقك في مشروعك.
لقد أدرجنا إصدارًا من تطبيق TodoMVC في رمز الرمز المرجعي في ملف todomvc. المجلد. انسخ جميع الملفات (بما في ذلك المجلدات) من todomvc إلى مجلد مشروعك.
سيُطلب منك استبدال ملف index.html. لا تتردّد في قبول العرض.
من المفترض أن تكون لديك الآن بنية الملف التالية في مجلد التطبيق:
الملفات المميزة باللون الأزرق هي من مجلد todomvc.
أعِد تحميل تطبيقك الآن (انقر بزر الماوس الأيمن > إعادة تحميل التطبيق). ينبغي أن تظهر لك واجهة المستخدم الأساسية قادرًا على إضافة مهامك.
جعل النصوص البرمجية تمتثل لسياسة أمان المحتوى (CSP)
افتح "وحدة التحكّم في أدوات مطوّري البرامج" (انقر بزر الماوس الأيمن > فحص العنصر، ثم اختَر علامة التبويب وحدة التحكّم). إِنْتَ سيظهر لك خطأ حول رفض تنفيذ نص برمجي مضمّن:
يمكننا إصلاح هذا الخطأ من خلال جعل التطبيق متوافقًا مع سياسة أمان المحتوى. ومن بين أكثر
يرجع سبب عدم امتثال سياسة أمان المحتوى (CSP) الشائع إلى JavaScript المضمَّنة. تتضمن أمثلة JavaScript المضمنة
معالِجات الأحداث كسمات DOM (مثل <button onclick=''>
) وعلامات <script>
مع محتوى
داخل HTML.
الحل بسيط: نقل المحتوى المضمّن إلى ملف جديد.
1. بالقرب من أسفل index.html، أزِل JavaScript المضمّن وضمِّن بدلاً من ذلك js/bootstrap.js:
<script src="bower_components/director/build/director.js"></script>
<script>
// Bootstrap app data
window.app = {};
</script>
<script src="js/bootstrap.js"></script>
<script src="js/helpers.js"></script>
<script src="js/store.js"></script>
2. أنشئ ملفًا في مجلد js باسم bootstrap.js. نقل الرمز المضمَّن سابقًا في هذا الملف:
// Bootstrap app data
window.app = {};
سيظل لديك تطبيق Todo لا يعمل إذا أعدت تحميل التطبيق الآن، لكنك على وشك الانتهاء.
تحويل LocalStorage إلى chrome.storage.local
إذا فتحت وحدة التحكّم في أدوات مطوّري البرامج الآن، من المفترض أن يكون الخطأ السابق قد اختفى. حدث خطأ جديد،
ومع ذلك، حول عدم توفر window.localStorage
:
لا تدعم تطبيقات Chrome استخدام localStorage
لأن localStorage
متزامن. الوصول المتزامن
قد يؤدي حظر الموارد (I/O) في بيئة تشغيل تتضمن سلسلة محادثات واحدة إلى عدم استجابة التطبيق.
تحتوي تطبيقات Chrome على واجهة برمجة تطبيقات مكافئة يمكنها تخزين العناصر بشكل غير متزامن. سيساعد هذا في تجنب عملية تسلسل الكائنات المكلفة أحيانًا object->string->.
لمعالجة رسالة الخطأ في التطبيق، عليك تحويل localStorage
إلى
chrome.storage.local.
تحديث أذونات التطبيقات
لاستخدام chrome.storage.local
، عليك طلب إذن storage
. ضِمن
manifest.json، أضِف "storage"
إلى مصفوفة permissions
:
"permissions": ["storage"],
التعرُّف على معلومات عن local.storage.set() وlocal.storage.get()
لحفظ عناصر المهام واستردادها، يجب الاطّلاع على طريقتَي set()
وget()
واجهة برمجة تطبيقات chrome.storage
.
تقبل الطريقة set() كائنًا من أزواج المفتاح/القيمة كمَعلمتها الأولى. حقل اختياري دالة الاستدعاء هي المعلمة الثانية. على سبيل المثال:
chrome.storage.local.set({secretMessage:'Psst!',timeSet:Date.now()}, function() {
console.log("Secret message saved");
});
تقبل طريقة get() المَعلمة الأولى الاختيارية لمفاتيح مخزن البيانات التي تريد. بالتراجع. يمكن تمرير مفتاح واحد كسلسلة؛ يمكن ترتيب مفاتيح متعددة في صفيف من سلاسل أو كائن قاموس.
أما المعلمة الثانية المطلوبة، فهي دالة استدعاء. في الكائن الذي تم إرجاعه، استخدِم المطلوبة في المعلمة الأولى للوصول إلى القيم المخزنة. على سبيل المثال:
chrome.storage.local.get(['secretMessage','timeSet'], function(data) {
console.log("The secret message:", data.secretMessage, "saved at:", data.timeSet);
});
إذا أردت get()
كل المحتوى المتوفّر حاليًا في chrome.storage.local
، احذف الأول.
:
chrome.storage.local.get(function(data) {
console.log(data);
});
على عكس localStorage
، لن تتمكن من فحص العناصر المخزّنة محليًا باستخدام "أدوات مطوري البرامج".
لوحة الموارد. ومع ذلك، يمكنك التفاعل مع chrome.storage
من وحدة تحكم JavaScript مثل
لذلك:
معاينة التغييرات المطلوبة في واجهة برمجة التطبيقات
تمثّل معظم الخطوات المتبقية في تحويل تطبيق Todo تغييرات بسيطة في طلبات البيانات من واجهة برمجة التطبيقات. جارٍ التغيير
جميع الأماكن التي يتم فيها استخدام localStorage
حاليًا، على الرغم من أنّها تستغرق وقتًا طويلاً وتكون عرضة للخطأ،
مطلوبة.
تنشأ الاختلافات الرئيسية بين localStorage
وchrome.storage
عن طبيعة
chrome.storage
:
بدلاً من الكتابة إلى
localStorage
باستخدام مهمة بسيطة، يمكنك استخدامchrome.storage.local.set()
مع استدعاءات اختيارية.var data = { todos: [] }; localStorage[dbName] = JSON.stringify(data);
مقارنة بـ
var storage = {}; storage[dbName] = { todos: [] }; chrome.storage.local.set( storage, function() { // optional callback });
بدلاً من الوصول إلى
localStorage[myStorageName]
مباشرةً، عليك استخدامchrome.storage.local.get(myStorageName,function(storage){...})
ثم تحليل القيم التي تم إرجاعها الكائنstorage
في دالة رد الاتصال.var todos = JSON.parse(localStorage[dbName]).todos;
مقارنة بـ
chrome.storage.local.get(dbName, function(storage) { var todos = storage[dbName].todos; });
يتم استخدام الدالة
.bind(this)
في جميع عمليات الاستدعاء للتأكد من أنthis
يشير إلىthis
النموذج الأوّلي "Store
" (يمكن العثور على مزيد من المعلومات حول الدوال المرتبطة في مستندات MDN: Function.prototype.bind().)function Store() { this.scope = 'inside Store'; chrome.storage.local.set( {}, function() { console.log(this.scope); // outputs: 'undefined' }); } new Store();
مقارنة بـ
function Store() { this.scope = 'inside Store'; chrome.storage.local.set( {}, function() { console.log(this.scope); // outputs: 'inside Store' }.bind(this)); } new Store();
ضع هذه الاختلافات الرئيسية في الاعتبار أثناء تناولنا لاسترداد عناصر المهام وحفظها وإزالتها في الأقسام التالية.
استرداد عناصر المهام
لنبدأ تحديث تطبيق Todo لاسترداد عناصر المهام:
1. تتولى طريقة الدالة الإنشائية Store
إعداد تطبيق Todo باستخدام جميع
عناصر المهام من مخزن البيانات. تتحقق الطريقة أولاً من وجود مخزن البيانات. إذا لم يحدث ذلك،
إنشاء صفيف فارغ من todos
وحفظه في مخزن البيانات حتى لا تكون هناك أخطاء في القراءة في بيئة التشغيل
في js/store.js، يمكنك تحويل استخدام localStorage
في طريقة الدالة الإنشائية لاستخدام
chrome.storage.local
:
function Store(name, callback) {
var data;
var dbName;
callback = callback || function () {};
dbName = this._dbName = name;
if (!localStorage[dbName]) {
data = {
todos: []
};
localStorage[dbName] = JSON.stringify(data);
}
callback.call(this, JSON.parse(localStorage[dbName]));
chrome.storage.local.get(dbName, function(storage) {
if ( dbName in storage ) {
callback.call(this, storage[dbName].todos);
} else {
storage = {};
storage[dbName] = { todos: [] };
chrome.storage.local.set( storage, function() {
callback.call(this, storage[dbName].todos);
}.bind(this));
}
}.bind(this));
}
2. يتم استخدام الطريقة find()
عند قراءة المهام من النموذج. تتغير النتائج التي تم إرجاعها استنادًا إلى
فيما إذا كنت تقوم بالتصفية حسب "الكل" أو "نشط" أو "مكتمل".
تحويل find()
لاستخدام chrome.storage.local
:
Store.prototype.find = function (query, callback) {
if (!callback) {
return;
}
var todos = JSON.parse(localStorage[this._dbName]).todos;
callback.call(this, todos.filter(function (todo) {
chrome.storage.local.get(this._dbName, function(storage) {
var todos = storage[this._dbName].todos.filter(function (todo) {
for (var q in query) {
return query[q] === todo[q];
}
});
callback.call(this, todos);
}.bind(this));
}));
};
3- على غرار "find()
"، يحصل "findAll()
" على جميع المهام من النموذج. تحويل findAll()
لاستخدامه
chrome.storage.local
:
Store.prototype.findAll = function (callback) {
callback = callback || function () {};
callback.call(this, JSON.parse(localStorage[this._dbName]).todos);
chrome.storage.local.get(this._dbName, function(storage) {
var todos = storage[this._dbName] && storage[this._dbName].todos || [];
callback.call(this, todos);
}.bind(this));
};
حفظ عناصر المهام
تمثّل طريقة save()
الحالية تحديًا. يعتمد ذلك على عمليتين غير متزامنتين (الإحضار والضبط)
التي تعمل على مساحة تخزين JSON الأحادية بأكملها في كل مرة أي تحديثات مجمَّعة على أكثر من عنصر
بنود قائمة المهام، مثل "وضع علامة على جميع المهام كمكتملة"، إلى خطر البيانات
القراءة بعد الكتابة: لم تحدث هذه المشكلة إذا كنا نستخدم مساحة تخزين أكثر ملاءمة للبيانات،
مثل IndexedDB، ولكنّنا نحاول تقليل جهد التحويل لهذا الدرس التطبيقي حول الترميز.
هناك عدة طرق لحلّ هذه المشكلة، وبالتالي سنستخدم هذه الفرصة لإعادة هيكلة save()
قليلاً حسب
الحصول على مصفوفة من معرّفات المهام ليتم تحديثها مرة واحدة:
1. للبدء، يجب لف كل العناصر داخل save()
باستخدام chrome.storage.local.get()
.
رد الاتصال:
Store.prototype.save = function (id, updateData, callback) {
chrome.storage.local.get(this._dbName, function(storage) {
var data = JSON.parse(localStorage[this._dbName]);
// ...
if (typeof id !== 'object') {
// ...
}else {
// ...
}
}.bind(this));
};
2. تحويل كل المثيلات البالغ عددها localStorage
باستخدام chrome.storage.local
:
Store.prototype.save = function (id, updateData, callback) {
chrome.storage.local.get(this._dbName, function(storage) {
var data = JSON.parse(localStorage[this._dbName]);
var data = storage[this._dbName];
var todos = data.todos;
callback = callback || function () {};
// If an ID was actually given, find the item and update each property
if ( typeof id !== 'object' ) {
// ...
localStorage[this._dbName] = JSON.stringify(data);
callback.call(this, JSON.parse(localStorage[this._dbName]).todos);
chrome.storage.local.set(storage, function() {
chrome.storage.local.get(this._dbName, function(storage) {
callback.call(this, storage[this._dbName].todos);
}.bind(this));
}.bind(this));
} else {
callback = updateData;
updateData = id;
// Generate an ID
updateData.id = new Date().getTime();
localStorage[this._dbName] = JSON.stringify(data);
callback.call(this, [updateData]);
chrome.storage.local.set(storage, function() {
callback.call(this, [updateData]);
}.bind(this));
}
}.bind(this));
};
3- بعد ذلك، عدِّل المنطق للعمل على مصفوفة بدلاً من عنصر واحد:
Store.prototype.save = function (id, updateData, callback) {
chrome.storage.local.get(this._dbName, function(storage) {
var data = storage[this._dbName];
var todos = data.todos;
callback = callback || function () {};
// If an ID was actually given, find the item and update each property
if ( typeof id !== 'object' || Array.isArray(id) ) {
var ids = [].concat( id );
ids.forEach(function(id) {
for (var i = 0; i < todos.length; i++) {
if (todos[i].id == id) {
for (var x in updateData) {
todos[i][x] = updateData[x];
}
}
}
});
chrome.storage.local.set(storage, function() {
chrome.storage.local.get(this._dbName, function(storage) {
callback.call(this, storage[this._dbName].todos);
}.bind(this));
}.bind(this));
} else {
callback = updateData;
updateData = id;
// Generate an ID
updateData.id = new Date().getTime();
todos.push(updateData);
chrome.storage.local.set(storage, function() {
callback.call(this, [updateData]);
}.bind(this));
}
}.bind(this));
};
وضع علامة "مكتملة" على عناصر المهام
الآن بما أن التطبيق يعمل على الصفائف، تحتاج إلى تغيير كيفية تعامل التطبيق مع مستخدم ينقر على زر محو المهام المكتملة (#):
1. في controller.js، يمكنك تعديل toggleAll()
لطلب toggleComplete()
مرة واحدة فقط باستخدام مصفوفة.
بدلاً من وضع علامة على إحدى المهام كمكتملة واحدة تلو الأخرى. حذف المكالمة الواردة إلى "_filter()
" أيضًا
لأنّه سيتم تعديل toggleComplete
_filter()
.
Controller.prototype.toggleAll = function (e) {
var completed = e.target.checked ? 1 : 0;
var query = 0;
if (completed === 0) {
query = 1;
}
this.model.read({ completed: query }, function (data) {
var ids = [];
data.forEach(function (item) {
this.toggleComplete(item.id, e.target, true);
ids.push(item.id);
}.bind(this));
this.toggleComplete(ids, e.target, false);
}.bind(this));
this._filter();
};
2. يمكنك الآن تعديل toggleComplete()
لقبول كل من مهمة واحدة أو مجموعة من المهام. وتشمل هذه المعلومات ما يلي:
نقل filter()
ليكون داخل update()
، بدلاً من خارجه.
Controller.prototype.toggleComplete = function (ids, checkbox, silent) {
var completed = checkbox.checked ? 1 : 0;
this.model.update(ids, { completed: completed }, function () {
if ( ids.constructor != Array ) {
ids = [ ids ];
}
ids.forEach( function(id) {
var listItem = $$('[data-id="' + id + '"]');
if (!listItem) {
return;
}
listItem.className = completed ? 'completed' : '';
// In case it was toggled from an event and not by clicking the checkbox
listItem.querySelector('input').checked = completed;
});
if (!silent) {
this._filter();
}
}.bind(this));
};
Count todo items
After switching to async storage, there is a minor bug that shows up when getting the number of todos. You'll need to wrap the count operation in a callback function:
1. In model.js, update getCount()
to accept a callback:
Model.prototype.getCount = function (callback) {
var todos = {
active: 0,
completed: 0,
total: 0
};
this.storage.findAll(function (data) {
data.each(function (todo) {
if (todo.completed === 1) {
todos.completed++;
} else {
todos.active++;
}
todos.total++;
});
if (callback) callback(todos);
});
return todos;
};
2. Back in controller.js, update _updateCount()
to use the async getCount()
you edited in
the previous step:
Controller.prototype._updateCount = function () {
var todos = this.model.getCount();
this.model.getCount(function(todos) {
this.$todoItemCounter.innerHTML = this.view.itemCounter(todos.active);
this.$clearCompleted.innerHTML = this.view.clearCompletedButton(todos.completed);
this.$clearCompleted.style.display = todos.completed > 0 ? 'block' : 'none';
this.$toggleAll.checked = todos.completed === todos.total;
this._toggleFrame(todos);
}.bind(this));
};
You are almost there! If you reload the app now, you will be able to insert new todos without any console errors.
Remove todos items
Now that the app can save todo items, you're close to being done! You still get errors when you attempt to remove todo items:
1. In store.js, convert all the localStorage
instances to use chrome.storage.local
:
a) To start off, wrap everything already inside remove()
with a get()
callback:
Store.prototype.remove = function (id, callback) {
chrome.storage.local.get(this._dbName, function(storage) {
var data = JSON.parse(localStorage[this._dbName]);
var todos = data.todos;
for (var i = 0; i < todos.length; i++) {
if (todos[i].id == id) {
todos.splice(i, 1);
break;
}
}
localStorage[this._dbName] = JSON.stringify(data);
callback.call(this, JSON.parse(localStorage[this._dbName]).todos);
}.bind(this));
};
b) Then convert the contents within the get()
callback:
Store.prototype.remove = function (id, callback) {
chrome.storage.local.get(this._dbName, function(storage) {
var data = JSON.parse(localStorage[this._dbName]);
var data = storage[this._dbName];
var todos = data.todos;
for (var i = 0; i < todos.length; i++) {
if (todos[i].id == id) {
todos.splice(i, 1);
break;
}
}
localStorage[this._dbName] = JSON.stringify(data);
callback.call(this, JSON.parse(localStorage[this._dbName]).todos);
chrome.storage.local.set(storage, function() {
callback.call(this, todos);
}.bind(this));
}.bind(this));
};
2. The same Read-After-Write data hazard issue previously present in the save()
method is also
present when removing items so you will need to update a few more places to allow for batch
operations on a list of todo IDs.
a) Still in store.js, update remove()
:
Store.prototype.remove = function (id, callback) {
chrome.storage.local.get(this._dbName, function(storage) {
var data = storage[this._dbName];
var todos = data.todos;
var ids = [].concat(id);
ids.forEach( function(id) {
for (var i = 0; i < todos.length; i++) {
if (todos[i].id == id) {
todos.splice(i, 1);
break;
}
}
});
chrome.storage.local.set(storage, function() {
callback.call(this, todos);
}.bind(this));
}.bind(this));
};
b) In controller.js, change removeCompletedItems()
to make it call removeItem()
on all IDs
at once:
Controller.prototype.removeCompletedItems = function () {
this.model.read({ completed: 1 }, function (data) {
var ids = [];
data.forEach(function (item) {
this.removeItem(item.id);
ids.push(item.id);
}.bind(this));
this.removeItem(ids);
}.bind(this));
this._filter();
};
c) Finally, still in controller.js, change the removeItem()
to support removing multiple items
from the DOM at once, and move the _filter()
call to be inside the callback:
Controller.prototype.removeItem = function (id) {
this.model.remove(id, function () {
var ids = [].concat(id);
ids.forEach( function(id) {
this.$todoList.removeChild($$('[data-id="' + id + '"]'));
}.bind(this));
this._filter();
}.bind(this));
this._filter();
};
إفلات كل عناصر المهام
تتوفّر طريقة أخرى في store.js باستخدام localStorage
:
Store.prototype.drop = function (callback) {
localStorage[this._dbName] = JSON.stringify({todos: []});
callback.call(this, JSON.parse(localStorage[this._dbName]).todos);
};
لم يتم استدعاء هذه الطريقة في التطبيق الحالي، لذا إذا كنت ترغب في تحدٍ إضافي، فحاول
وتنفيذه بنفسك. تلميح: ألقِ نظرة على chrome.storage.local.clear()
.
فتح تطبيق Todo المكتمل
لقد انتهيت من الخطوة 2. أعِد تحميل تطبيقك من المفترض أن يكون لديك الآن إصدار Chrome مجمّع يعمل بكامل طاقته. TodoMVC.
لمزيد من المعلومات
لمزيد من المعلومات التفصيلية حول بعض واجهات برمجة التطبيقات التي تم تقديمها في هذه الخطوة، راجع:
- سياسة أمان المحتوى ↑
- الإفصاح عن الأذونات ↑
- chrome.storage ↑
- chrome.storage.local.get() ↑
- chrome.storage.local.set() ↑
- chrome.storage.local.remove() ↑
- chrome.storage.local.clear() ↑
هل أنت مستعد للانتقال إلى الخطوة التالية؟ يُرجى الانتقال إلى الخطوة 3 - إضافة المنبّهات والإشعارات »