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.
  • Uygulamanızın komut dosyalarını İçerik Güvenliği Politikası'na (İGP) uygun hale getirme
  • chrome.storage.local'den yararlanarak yerel depolama nasıl uygulanır?

Bu adımın tamamlanması için tahmini süre: 20 dakika.
. Bu adımda tamamlayacaklarınızı önizlemek için bu sayfanın en altına gidin ↓.

Mevcut bir Todo uygulamasını içe aktarma

Başlangıç olarak, yaygın bir karşılaştırma olan TodoMVC'nin vanilya JavaScript sürümünü içe aktarın. projenize ekleyin.

TodoMVC uygulamasının bir sürümünü, todomvc içindeki referans kodu posta kodu bölümüne ekledik. tıklayın. todomvc dosyasındaki tüm dosyaları (klasörler dahil) proje klasörünüze kopyalayın.

todomvc klasörünü codelab klasörüne kopyalayın

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

index.html'yi değiştirin

Uygulama klasörünüzde artık 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 çok iyi olur.

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, ardından Konsol sekmesini seçin). Siz satır içi komut dosyasını çalıştırmayı reddetmeyle ilgili bir hata mesajı görür:

CSP konsolu günlüğü hatası olan Todo uygulaması

Uygulamayı İçerik Güvenliği Politikası'na uygun hale getirerek bu hatayı düzeltelim. Google'ın en büyük yaygın CSP uyumsuzlukları satır içi JavaScript'ten kaynaklanır. Satır içi JavaScript örnekleri aşağıdakileri içerir: DOM özellikleri (ör. <button onclick=''>) ve içeriği olan <script> etiketleri olarak etkinlik işleyiciler içine gireceğim.

Çözüm basittir: satır içi içeriği yeni bir dosyaya taşıyın.

1. index.html sayfası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:

<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 taşıyın şu dosyada yer almalıdır:

// 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ür

Geliştirici Araçları Konsolu'nu şimdi açarsanız önceki hata kaybolacaktır. Yeni bir hata var, ancak, window.localStorage adlı müşterinin mevcut olmaması hakkında:

LocalStorage konsolu günlüğündeki yapılacaklar listesi hatası

localStorage eşzamanlı olduğundan Chrome Uygulamaları localStorage uygulamasını desteklemez. Eşzamanlı erişim tek iş parçacıklı bir çalışma zamanında kaynakları (G/Ç) engellemek, uygulamanızın yanıt vermemesine neden olabilir.

Chrome Uygulamaları, nesneleri eşzamansız olarak depolayabilen eşdeğer bir API'ye sahiptir. Bu sayede karşılaşabileceğiniz nesne->string->nesne serileştirme işlemi bazen maliyetli olur.

Uygulamamızdaki hata mesajıyla ilgilenmek için localStorage öğesini şuna dönüştürmeniz gerekiyor: chrome.storage.local gibi daha fazla depolama alanı kullanabilirsiniz.

Uygulama izinlerini güncelleme

chrome.storage.local ürününü kullanmak için storage iznini istemeniz gerekir. İçinde manifest.json dosyası için permissions dizisine "storage" ekleyin:

"permissions": ["storage"],

local.storage.set() ve local.storage.get() hakkında bilgi edinin.

Yapılacak işler öğelerini kaydetmek ve almak için öğelerin set() ve get() yöntemleri hakkında bilgi sahibi olmanız gerekir. chrome.storage API.

set() yöntemi, ilk parametresi olarak anahtar/değer çiftleri nesnesini kabul eder. İsteğe bağlı 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, istediğiniz veri deposu anahtarları için isteğe bağlı bir ilk parametreyi kabul eder. geri dönelim. Tek bir anahtar bir dize olarak iletilebilir; birden fazla anahtar, dizeler veya sözlük nesnesi oluşturur.

Gerekli olan ikinci parametre, bir geri çağırma işlevidir. Döndürülen nesnede, şunu kullanın: depolanan değerlere erişmek için ilk parametrede istenen anahtar sayısı. Ö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 olan her şeyi get() eklemek istiyorsanız ilkini atlayın parametresi:

chrome.storage.local.get(function(data) {
  console.log(data);
});

localStorage hizmetinin aksine, Yerel olarak depolanan öğeleri Geliştirici Araçları'nı kullanarak inceleyemezsiniz. Kaynaklar paneli. Bununla birlikte, chrome.storage ile JavaScript Konsolu'ndan şunun gibi etkileşimde bulunabilirsiniz: dolayısıyla:

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

Gerekli API değişikliklerini önizleyin

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. Değiştiriliyor localStorage ürününün şu anda kullanıldığı tüm yerler; zaman alıcı ve hataya açık olsa da, gereklidir.

localStorage ile chrome.storage arasındaki temel farklar, chrome.storage:

  • localStorage ile basit ödevler istemek yerine İsteğe bağlı geri çağırma işlevleriyle chrome.storage.local.set().

    var data = { todos: [] };
    localStorage[dbName] = JSON.stringify(data);
    

    karşı

    var storage = {};
    storage[dbName] = { todos: [] };
    chrome.storage.local.set( storage, function() {
      // optional callback
    });
    
  • localStorage[myStorageName] hizmetine doğrudan erişmek yerine chrome.storage.local.get(myStorageName,function(storage){...}) ve ardından döndürülen storage nesnesini içeriyor.

    var todos = JSON.parse(localStorage[dbName]).todos;
    

    karşı

    chrome.storage.local.get(dbName, function(storage) {
      var todos = storage[dbName].todos;
    });
    
  • .bind(this) işlevi, tüm geri çağırmalarda kullanılır. Böylece this,this Store prototipi. (Bağlı işlevler hakkında daha fazla bilgi MDN belgelerinde 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();
    

bu bölümde bulabilirsiniz.

Yapılacaklar listesi öğelerini alma

Yapılacaklar listesi öğelerini almak için Todo uygulamasını güncelleyelim:

1. Store oluşturucu yöntemi, Todo uygulamasını mevcut tüm öğeler atayacağım. Yöntem ilk olarak veri deposunun olup olmadığını kontrol eder. Aksi takdirde boş bir todos dizisi oluşturup çalışma zamanı okuma hatası olmaması için bunu veri deposuna kaydedin.

js/store.js'de, oluşturucu yöntemde localStorage kullanımını bunun yerine 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. Modelden yapılacak işler okunurken find() yöntemi kullanılır. Döndürülen sonuçlar şuna göre değişir: "Tümü", "Etkin" veya "Tamamlandı" ölçütüne göre filtreleme yaptığınızdan emin olun.

find() değerini, chrome.storage.local kullanacak şekilde 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() özelliğine benzer şekilde, findAll() tüm yapılacak işleri Model'den alır. findAll() metnini kullanmak için dönüştürün 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));
};

Yapılacaklar listesi öğelerini kaydet

Mevcut save() yöntemi bazı zorluklar içeriyor. İki eşzamansız işleme (al ve ayarla) bağlıdır monolitik JSON depolama alanının tamamında çalışan uygulamalar. Birden fazla cihazdaki toplu güncellemeler "yapılacak tüm işleri tamamlandı olarak işaretle" gibi bir işlem öğesi, Yazma Sonrası Okuma. Daha uygun bir veri depolama alanı kullanıyor olsaydık bu sorun ancak bu codelab için dönüşüm çabasını en aza indirmeye çalışıyoruz.

Bu sorunu düzeltmenin birkaç yolu olduğundan bu fırsatı değerlendirerek save() ile ilgili aynı anda güncellenecek bir dizi yapılacaklar listesi öğesi alır:

1. Başlamak için zaten save() içindeki her şeyi bir chrome.storage.local.get() ile sarmalayın geri arama:

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, tek bir öğe yerine bir dizide çalışacak şekilde mantığı 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ılacak öğeleri tamamlandı olarak işaretle

Uygulama artık diziler üzerinde çalıştığına göre Tamamlananları temizle (#) düğmesi:

1. controller.js'de, toggleAll() öğesini bir diziyle yalnızca bir kez toggleComplete() çağrısı yapacak şekilde güncelleyin. tek tek tamamlandı olarak işaretlemek yerine, yapılacak işler listesine ekleyeceksiniz. _filter() adlı kişiye yapılan aramayı da sil çünkü toggleComplete _filter() ayarını yapacaksınız.

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 dizi yapılacak işi kabul edecek şekilde güncelleyin. Buna şunlar dâhildir: filter(), dışarı değil update() içinde olacak şekilde taşınıyor.

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();
};

Yapılacak tüm öğeleri bırak

store.js'de localStorage kullanan 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. Daha fazla sorgulamak isterseniz uygulayıp ona göre hareket edebilirsiniz. İpucu: chrome.storage.local.clear() sitesine göz atın.

Tamamlanmış Todo uygulamanızı başlatın

2. adımı tamamladınız! Uygulamanızı yeniden yüklediğinizde Chrome'un tam olarak çalışan bir paket sürümüne sahip olmanız gerekir. .

Daha fazla bilgi için

Bu adımda tanıtılan API'lerin bazıları hakkında daha ayrıntılı bilgi için aşağıdaki konulara bakın:

Bir sonraki adıma geçmeye hazır mısınız? 3. Adım - Alarm ve bildirim ekleme » bölümüne gidin.