Bu adımda şunları öğreneceksiniz:
- Mevcut bir web uygulaması Chrome Uygulamaları platformu için nasıl uyarlanır?
- Uygulama komut dosyalarınızı İçerik Güvenliği Politikası (İGP) ile uyumlu hale getirme.
- chrome.storage.local kullanılarak yerel depolama nasıl uygulanır?
Bu adımın tamamlanması için tahmini süre: 20 dakika.
Bu adımda ne tamamlayacağınızı önizlemek için bu sayfanın en altına atlayın ↓.
Mevcut bir Todo uygulamasını içe aktarma
Başlangıç olarak, yaygın bir karşılaştırma uygulaması olan TodoMVC'nin vanilla JavaScript sürümünü projenize aktarın.
TodoMVC uygulamasının bir sürümünü todomvc klasöründeki referans kodu posta koduna ekledik. todomvc'deki tüm dosyaları (klasörler dahil) proje klasörünüze kopyalayın.
index.html dosyasını değiştirmeniz istenir. Devam et ve kabul et.
Artık uygulama klasörünüzde aşağıdaki dosya yapısına sahip olmanız gerekir:
Maviyle vurgulanan dosyalar todomvc klasöründen alınır.
Uygulamanızı hemen yeniden yükleyin (sağ tıklayın > Uygulamayı Yeniden Yükle). Temel kullanıcı arayüzünü görürsünüz ancak Yapılacaklar ekleyemezsiniz.
Komut dosyalarını İçerik Güvenliği Politikası (İGP) ile uyumlu hale getirme
Geliştirici Araçları Konsolu'nu açın (sağ tıklayın > Öğeyi İncele'yi, ardından Konsol sekmesini seçin). Satır içi bir komut dosyasını yürütmenin reddedilmesiyle ilgili bir hata görürsünüz:
Uygulamayı İçerik Güvenliği Politikası uyumlu hale getirerek bu hatayı düzeltelim. En sık karşılaşılan CSP uyumsuzluklarından biri satır içi JavaScript'ten kaynaklanır. Satır içi JavaScript'e örnek olarak DOM özellikleri olarak etkinlik işleyiciler (ör. <button onclick=''>
) ve HTML içinde içeriği olan <script>
etiketleri verilebilir.
Çözüm oldukça basit: Satır içi içeriği yeni bir dosyaya taşıyın.
1. index.html dosyasının alt kısmına yakın bir yerde, satır içi JavaScript'i kaldırın ve bunun yerine js/bootstrap.js öğesini ekleyin:
<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 klasöründe bootstrap.js adlı bir dosya oluşturun. Önceki satır içi kodu bu dosyanın içine taşıyın:
// Bootstrap app data
window.app = {};
Uygulamayı şimdi yeniden yüklerseniz ancak yaklaşırsanız çalışmayan Todo uygulamanız çalışmaya devam eder.
localStorage'ı chrome.storage.local biçimine dönüştürme
Geliştirici Araçları Konsolu'nu şimdi açarsanız önceki hata giderilmiş olur. Ancak window.localStorage
ürününün mevcut olmamasıyla ilgili yeni bir hata var:
localStorage
eşzamanlı olduğundan Chrome Uygulamaları localStorage
'i desteklemez. Tek iş parçacıklı bir çalışma zamanındaki engelleme kaynaklarına (G/Ç) eşzamanlı erişim, uygulamanızı yanıt vermemesine neden olabilir.
Chrome Uygulamaları, nesneleri eşzamansız olarak depolayabilen eşdeğer bir API'ye sahiptir. Bu, bazen maliyetli nesne->dize->nesne serileştirme sürecini önlemeye yardımcı olur.
Uygulamamızdaki hata mesajını düzeltmek için localStorage
dosyasını chrome.storage.local biçimine dönüştürmeniz gerekir.
Uygulama izinlerini güncelleme
chrome.storage.local
uygulamasını kullanmak için storage
iznini istemeniz gerekir. manifest.json dosyasında, permissions
dizisine "storage"
ekleyin:
"permissions": ["storage"],
local.storage.set() ve local.storage.get() hakkında bilgi edinin
Yapılacaklar listesini kaydetmek ve almak için chrome.storage
API'nin set()
ve get()
yöntemlerini bilmeniz gerekir.
set() yöntemi, anahtar/değer çiftlerinden oluşan bir nesneyi ilk parametre olarak kabul eder. İsteğe bağlı bir geri çağırma işlevi de ikinci parametredir. Örneğin:
chrome.storage.local.set({secretMessage:'Psst!',timeSet:Date.now()}, function() {
console.log("Secret message saved");
});
get() yöntemi, almak istediğiniz veri deposu anahtarları için isteğe bağlı bir ilk parametreyi kabul eder. Tek bir anahtar dize olarak iletilebilir. Birden fazla anahtar bir dize dizisi veya sözlük nesnesi olarak düzenlenebilir.
Zorunlu olan ikinci parametre, bir geri çağırma işlevidir. Döndürülen nesnede, depolanan değerlere erişmek için ilk parametrede istenen anahtarları kullanın. Örneğin:
chrome.storage.local.get(['secretMessage','timeSet'], function(data) {
console.log("The secret message:", data.secretMessage, "saved at:", data.timeSet);
});
Şu anda chrome.storage.local
içinde bulunan her şeyi get()
yapmak istiyorsanız ilk parametreyi çıkarın:
chrome.storage.local.get(function(data) {
console.log(data);
});
localStorage
'ın aksine, Geliştirici Araçları Kaynaklar panelini kullanarak yerel olarak depolanan öğeleri inceleyemezsiniz. Ancak JavaScript Konsolu'ndan chrome.storage
ile şu şekilde etkileşim kurabilirsiniz:
Gerekli API değişikliklerini önizleyin
Todo uygulamasını dönüştürmenin geri kalan adımlarının çoğu, API çağrılarında yapılan küçük değişikliklerdir. Zaman alıcı ve hataya açık olsa da localStorage
uygulamasının kullanıldığı tüm yerlerin değiştirilmesi gerekir.
localStorage
ile chrome.storage
arasındaki temel farklar, chrome.storage
eşzamansız yapısından kaynaklanır:
Basit atama kullanarak
localStorage
işlevine yazmak yerine isteğe bağlı geri çağırmalarla birliktechrome.storage.local.set()
kullanmanız gerekir.var data = { todos: [] }; localStorage[dbName] = JSON.stringify(data);
ile
var storage = {}; storage[dbName] = { todos: [] }; chrome.storage.local.set( storage, function() { // optional callback });
localStorage[myStorageName]
öğesine doğrudan erişmek yerinechrome.storage.local.get(myStorageName,function(storage){...})
kullanmanız ve ardından döndürülenstorage
nesnesini geri çağırma işlevinde ayrıştırmanız gerekir.var todos = JSON.parse(localStorage[dbName]).todos;
ile
chrome.storage.local.get(dbName, function(storage) { var todos = storage[dbName].todos; });
.bind(this)
işlevi,this
öğesininStore
prototipininthis
öğesini belirttiğinden emin olmak için tüm geri çağırmalarda kullanılır. (Bağlı işlevler hakkında daha fazla bilgiyi MDN belgelerinde bulabilirsiniz: Function.prototype.bind().)function Store() { this.scope = 'inside Store'; chrome.storage.local.set( {}, function() { console.log(this.scope); // outputs: 'undefined' }); } new Store();
ile
function Store() { this.scope = 'inside Store'; chrome.storage.local.set( {}, function() { console.log(this.scope); // outputs: 'inside Store' }.bind(this)); } new Store();
Yapılacaklar listesindeki öğeleri alma, kaydetme ve kaldırma konularını ele alırken, bu temel farklılıkları aklınızda bulundurun.
Yapılacaklar listesi öğelerini al
Yapılacaklar listesini almak için Todo uygulamasını güncelleyelim:
1. Store
oluşturucu yöntemi, Todo uygulamasını veri deposundaki tüm mevcut yapılacaklar öğeleriyle başlatmayı tamamlar. Yöntem önce veri deposunun mevcut olup olmadığını kontrol eder. Aksi takdirde boş bir todos
dizisi oluşturur ve çalışma zamanı okuma hatası olmaması için bunu veri deposuna kaydeder.
js/store.js dosyasında, oluşturucu yöntemindeki localStorage
kullanımını, bunun yerine chrome.storage.local
kullanımına dönüştürün:
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. Modelden yapılacaklar listesi okunurken find()
yöntemi kullanılır. Döndürülen sonuçlar, "Tümü", "Etkin" veya "Tamamlandı" ölçütlerine göre filtreleme yapmanıza bağlı olarak değişir.
chrome.storage.local
kullanmak için find()
kodunu dönüştürün:
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()
ile benzer olan findAll()
, yapılacak tüm işleri Model'den alır. chrome.storage.local
kullanmak için findAll()
kodunu dönüştürün:
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));
};
Yapılacaklar listesindeki öğeleri kaydet
Mevcut save()
yöntemi zorluk teşkil ediyor. Bu, her seferinde monolitik JSON depolama alanında çalışan iki eşzamansız işleme (alıp ayarla) bağlıdır. Birden fazla yapılacaklar öğesinde (yapılacak işlerin tümünü tamamlandı olarak işaretle) yapılan toplu güncellemeler, Yazma Sonrası Okuma olarak bilinen veri tehlikesine yol açar. IndexedDB gibi daha uygun bir veri depolama kullanıyor olsaydık bu sorun olmazdı ama bu codelab için dönüşüm sürecini en aza indirmeye çalışıyoruz.
Bu sorunu düzeltmenin birkaç yolu vardır. Bu fırsatı değerlendirerek, tümünün güncellenmesini istediğiniz yapılacaklar listesi kimliklerinden oluşan bir diziyi alarak save()
özelliğini biraz yeniden düzenlemenizi öneririz:
1. Başlamak için halihazırda save()
içinde yer alan her şeyi chrome.storage.local.get()
geri çağırmasıyla sarmalayın:
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. chrome.storage.local
ile tüm localStorage
örneklerini dönüştürün:
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. Ardından, mantığı tek bir öğe yerine bir dizide çalışacak şekilde güncelleyin:
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));
};
Yapılacaklar listesindeki öğeleri tamamlandı olarak işaretle
Uygulama dizilerde çalıştığına göre, uygulamanın Tamamlananları temizle (#) düğmesini tıklayan kullanıcıları ele alma şeklini değiştirmeniz gerekir:
1. controller.js'de, toggleAll()
öğesini tek tek tamamlandı olarak işaretlemek yerine bir yapılacaklar dizisiyle yalnızca bir kez toggleComplete()
çağıracak şekilde güncelleyin. Ayrıca toggleComplete
_filter()
ayarlaması yapacağınız için _filter()
çağrısını da silin.
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. Şimdi toggleComplete()
uygulamasını hem tek bir yapılacak işi hem de bir yapılacak işler dizisini kabul edecek şekilde güncelleyin. Buna, filter()
öğesini dışa değil update()
içine taşımak dahildir.
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();
};
Tüm yapılacaklar listesi öğelerini bırak
store.js dosyasında localStorage
kullanan bir başka yöntem daha vardır:
Store.prototype.drop = function (callback) {
localStorage[this._dbName] = JSON.stringify({todos: []});
callback.call(this, JSON.parse(localStorage[this._dbName]).todos);
};
Bu yöntem mevcut uygulamada çağrılmadığından, ekstra bir zorluk isterseniz kendi başınıza uygulamayı deneyin. İpucu: chrome.storage.local.clear()
adresine bir göz atın.
Tamamlanmış Todo uygulamanızı başlatma
2. adımı tamamladınız! Uygulamanızı yeniden yüklediğinizde TodoMVC'nin tam olarak çalışan Chrome paket sürümüne sahip olursunuz.
Daha fazla bilgi için
Bu adımda tanıtılan API'lerden bazıları hakkında daha ayrıntılı bilgi için aşağıdaki makalelere bakın:
- İçerik Güvenliği Politikası ↑
- İzin Bildirme ↑
- chrome.storage ↑
- chrome.storage.local.get() ↑
- chrome.storage.local.set() ↑
- chrome.storage.local.remove() ↑
- chrome.storage.local.clear() ↑
Bir sonraki adıma geçmeye hazır mısınız? 3. Adım - Alarm ve bildirim ekleyin » bölümüne gidin