2. Adım: Mevcut Bir Web Uygulamasını İçe Aktarın

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.

todomvc klasörünü codelab klasörüne kopyala

index.html'yi değiştirmeniz istenir. Devam edin ve kabul edin.

index.html dosyasını değiştirme

Artık uygulama klasörünüzde aşağıdaki dosya yapısına sahip olmanız gerekir:

Yeni proje klasörü

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:

CSP konsol günlüğü hatası içeren yapılacaklar listesi uygulaması

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 konsolu günlüğündeki yapılacaklar listesi hatası

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:

chrome.storage adresinde hata ayıklamak için Console&#39;u kullanın

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ğırmalarla chrome.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 yerine chrome.storage.local.get(myStorageName,function(storage){...})'u kullanmanız ve ardından geri çağırma işlevinde döndürülen storage 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'un Store prototipinin this'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:

Todo app with localStorage console log error

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:

Sonraki adıma geçmeye hazır mısınız? 3. Adım - Alarm ve bildirim ekleme » başlıklı makaleyi inceleyin.