Bu adımda şunları öğreneceksiniz:
- Mevcut bir web uygulamasını Chrome Uygulamaları platformuna uyarlama.
- Uygulama komut dosyalarınızı İçerik Güvenliği Politikası (İGP) ile uyumlu hale getirme.
- chrome.storage.local kullanılarak yerel depolama alanı nasıl uygulanır?
Bu adımın tamamlanması tahmini olarak 20 dakika sürer.
Bu adımda tamamlayacaklarınızı önizlemek için bu sayfanın en altına gidin ↓.
Mevcut bir yapılacaklar listesini içe aktarma
Başlangıç noktası olarak, yaygın bir karşılaştırma uygulaması olan TodoMVC'nin basit JavaScript sürümünü projenize aktarın.
TodoMVC uygulamasının bir sürümünü todomvc bölümündeki referans kod zip dosyasına ekledik. todomvc klasöründeki tüm dosyaları (klasörler dahil) proje klasörünüze kopyalayın.
index.html'yi değiştirmeniz istenir. Devam edin ve kabul edin.
Artık uygulama klasörünüzde aşağıdaki dosya yapısına sahip olmanız gerekir:
Maviyle vurgulanan dosyalar todomvc klasöründendir.
Uygulamanızı şimdi yeniden yükleyin (sağ tıklayın > Uygulamayı Yeniden Yükle). Temel kullanıcı arayüzünü görürsünüz ancak yapılacak işleri ekleyemezsiniz.
Komut dosyalarını İçerik Güvenliği Politikası'na (İGP) uygun hale getirin
Geliştirici Araçları Konsolu'nu açın (sağ tıklayın > Öğeyi İncele'yi seçin, ardından Konsol sekmesini seçin). Satır içi komut dosyası yürütmenin reddedilmesiyle ilgili bir hata görürsünüz:
Uygulamayı İçerik Güvenliği Politikası'na uygun hale getirerek bu hatayı düzeltelim. CSP'ye uyulmama sorunlarının en yaygın nedenlerinden biri satır içi JavaScript'tir. Satır içi JavaScript örnekleri arasında DOM özellikleri olarak etkinlik işleyiciler (ör. <button onclick=''>
) ve HTML'de içerik barındıran <script>
etiketleri yer alır.
Çözüm basittir: Satır içi içeriği yeni bir dosyaya taşıyın.
1. index.html dosyasının alt kısmındaki satır içi JavaScript'i kaldırın ve yerine js/bootstrap.js dosyasını 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. Daha önce satır içi olan kodu bu dosyaya taşıyın:
// Bootstrap app data
window.app = {};
Uygulamayı şimdi yeniden yüklerseniz ancak çalışmaya yaklaştığınızda çalışmaya devam eder.
localStorage'ı chrome.storage.local olarak dönüştürme
DevTools Konsolu'nu şimdi açarsanız önceki hata ortadan kalkmış olur. Ancak window.localStorage
'ün kullanılamamasıyla ilgili yeni bir hata var:
localStorage
senkronize olduğundan Chrome uygulamaları localStorage
işlevini desteklemez. Tek iş parçacıklı bir çalışma zamanında engelleyen kaynaklara (G/Ç) senkronize erişim, uygulamanızın yanıt vermesini engelleyebilir.
Chrome uygulamaları, nesneleri eşzamansız olarak depolayabilen eşdeğer bir API'ye sahiptir. Bu sayede, bazen maliyetli olan nesne->dize->nesne serileştirme işlemini önleyebilirsiniz.
Uygulamamızdaki hata mesajını gidermek için localStorage
değerini chrome.storage.local olarak dönüştürmeniz gerekir.
Uygulama izinlerini güncelleme
chrome.storage.local
'ü kullanabilmek 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.
To Do listesi öğelerini kaydetmek ve almak için chrome.storage
API'nin set()
ve get()
yöntemleri hakkında bilgi sahibi olmanız gerekir.
set() yöntemi, ilk parametresi olarak anahtar/değer çiftleri nesnesi kabul eder. İsteğe bağlı bir geri çağırma işlevi, 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 kümesi anahtarları için isteğe bağlı bir ilk parametre kabul eder. Tek bir anahtar dize olarak iletilebilir; birden fazla anahtar, dize dizisi veya sözlük nesnesi olarak düzenlenebilir.
Gerekli 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()
etmek istiyorsanız ilk parametreyi çıkarın:
chrome.storage.local.get(function(data) {
console.log(data);
});
localStorage
'ten farklı olarak, DevTools Kaynaklar panelini kullanarak yerel olarak depolanan öğeleri inceleyemezsiniz. Ancak JavaScript Konsolu'ndan chrome.storage
ile şu şekilde etkileşimde bulunabilirsiniz:
Gerekli API değişikliklerini önizleme
Todo uygulamasını dönüştürmeyle ilgili geri kalan adımların çoğu, API çağrılarında yapılan küçük değişikliklerdir. localStorage
'ün şu anda kullanıldığı tüm yerleri değiştirmek zaman alıcı ve hatalara açık olsa da gereklidir.
localStorage
ile chrome.storage
arasındaki temel farklar, chrome.storage
'un eşzamansız yapısından kaynaklanır:
Basit atama kullanarak
localStorage
değerine yazmak yerine, isteğe bağlı geri çağırmalarlachrome.storage.local.set()
değerini kullanmanız gerekir.var data = { todos: [] }; localStorage[dbName] = JSON.stringify(data);
karşı
var storage = {}; storage[dbName] = { todos: [] }; chrome.storage.local.set( storage, function() { // optional callback });
localStorage[myStorageName]
'e doğrudan erişmek yerinechrome.storage.local.get(myStorageName,function(storage){...})
'u kullanmanız ve ardından geri çağırma işlevinde döndürülenstorage
nesnesini ayrıştırmanız gerekir.var todos = JSON.parse(localStorage[dbName]).todos;
karşı
chrome.storage.local.get(dbName, function(storage) { var todos = storage[dbName].todos; });
this
'unStore
prototipininthis
'una atıfta bulunduğundan emin olmak için tüm geri çağırmalarda.bind(this)
işlevi kullanılır. (Bağlı işlevler hakkında daha fazla bilgi MDN dokümanlarında bulunabilir: Function.prototype.bind().)function Store() { this.scope = 'inside Store'; chrome.storage.local.set( {}, function() { console.log(this.scope); // outputs: 'undefined' }); } new Store();
karşı
function Store() { this.scope = 'inside Store'; chrome.storage.local.set( {}, function() { console.log(this.scope); // outputs: 'inside Store' }.bind(this)); } new Store();
Aşağıdaki bölümlerde yapılacaklar öğelerini alma, kaydetme ve kaldırma konularını ele alırken bu temel farkları göz önünde bulundurun.
Yapılacaklar listesindeki öğeleri alma
Yapılacaklar listesi öğelerini almak için Todo uygulamasını güncelleyelim:
1. Store
kurucu yöntemi, Todo uygulamasını veri deposundaki tüm mevcut yapılacaklar öğeleriyle başlatır. Yöntem ilk olarak veri deposunun var olup olmadığını kontrol eder. Aksi takdirde, çalışma zamanında okuma hatası olmaması için todos
boş bir dizisi oluşturur ve veri deposuna kaydeder.
js/store.js dosyasında, kurucu yönteminde localStorage
yerine chrome.storage.local
kullanı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ılacak işler okunurken find()
yöntemi kullanılır. Döndürülen sonuçlar, "Tümü", "Etkin" veya "Tamamlandı"ya göre filtreleme yapıp yapmadığınıza bağlı olarak değişir.
find()
'ü chrome.storage.local
'a dönüştürmek için:
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()
'e benzer şekilde findAll()
, Model'den tüm yapılacaklar listesini alır. findAll()
değerini, chrome.storage.local
kullanacak şekilde 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));
};
To Do listesi öğelerini kaydetme
Mevcut save()
yöntemi bazı zorluklar içeriyor. Bu, her seferinde tek bir JSON depolama alanının tamamında çalışan iki eşzamansız işleme (get ve set) bağlıdır. Birden fazla yapılacaklar öğesinde yapılan toplu güncellemeler (ör. "tüm yapılacaklar öğelerini tamamlandı olarak işaretle") yazdıktan sonra okuma olarak bilinen bir veri tehlikesine neden olur. IndexedDB gibi daha uygun bir veri depolama alanı kullanıyor olsaydık bu sorunla karşılaşmazdık ancak bu kod laboratuvarının dönüşüm çabasını en aza indirmeye çalışıyoruz.
Bu sorunu düzeltmenin birkaç yolu vardır. Bu fırsatı değerlendirerek, bir kerede güncellenecek bir yapılacaklar listesi kimliği dizisi alarak save()
'ü biraz yeniden yapılandıracağız:
1. Başlamak için save()
içindeki 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. Tüm localStorage
örneklerini chrome.storage.local
ile 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 dizi üzerinde ç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şaretleme
Uygulama artık dizilerle çalıştığından, kullanıcının Tamamlananları temizle (#) düğmesini tıklamasını uygulamanın nasıl işlediğini değiştirmeniz gerekir:
1. controller.js dosyasında, toggleAll()
işlevini, yapılacaklar listesini tek tek tamamlandı olarak işaretlemek yerine toggleComplete()
işlevini yapılacaklar listesi dizisiyle yalnızca bir kez çağıracak şekilde güncelleyin. toggleComplete
_filter()
ayarını yapacağınız için _filter()
araması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. Ardından toggleComplete()
işlevini hem tek bir yapılacaklar listesi hem de yapılacaklar listesi dizisi kabul edecek şekilde güncelleyin. Buna filter()
öğesinin, dışarı değil, update()
içinde olacak şekilde taşınması da 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 öğelerini bırak
store.js dosyasında localStorage
kullanan başka bir 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ılmıyor. Bu nedenle, ek bir zorluk yaşamak istiyorsanız bu yöntemi kendiniz uygulamayı deneyin. İpucu: chrome.storage.local.clear()
sayfasına göz atın.
Tamamlanan yapılacaklar uygulamanızı başlatma
2. adımı tamamladınız! Uygulamanızı yeniden yükleyin. Artık TodoMVC'nin tamamen çalışan bir Chrome paketlenmiş sürümüne sahip olacaksınız.
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 makaleleri inceleyin:
- İçerik Güvenliği Politikası ↑
- İzinleri beyan etme ↑
- chrome.storage ↑
- chrome.storage.local.get() ↑
- chrome.storage.local.set() ↑
- chrome.storage.local.remove() ↑
- chrome.storage.local.clear() ↑
Sonraki adıma geçmeye hazır mısınız? 3. Adım - Alarm ve bildirim ekleme » başlıklı makaleyi inceleyin.