ধাপ 2: একটি বিদ্যমান ওয়েব অ্যাপ আমদানি করুন

এই ধাপে, আপনি শিখবেন:

  • Chrome Apps প্ল্যাটফর্মের জন্য একটি বিদ্যমান ওয়েব অ্যাপ্লিকেশন কীভাবে মানিয়ে নেওয়া যায়।
  • কিভাবে আপনার অ্যাপ স্ক্রিপ্ট কন্টেন্ট সিকিউরিটি পলিসি (CSP) অনুগত করবেন।
  • chrome.storage.local ব্যবহার করে কিভাবে স্থানীয় স্টোরেজ বাস্তবায়ন করবেন।

এই ধাপটি সম্পূর্ণ করার আনুমানিক সময়: 20 মিনিট।
এই ধাপে আপনি যা সম্পূর্ণ করবেন তার পূর্বরূপ দেখতে, এই পৃষ্ঠার নীচে ঝাঁপ দাও ↓

একটি বিদ্যমান Todo অ্যাপ আমদানি করুন

একটি সূচনা বিন্দু হিসাবে, TodoMVC- এর ভ্যানিলা জাভাস্ক্রিপ্ট সংস্করণ আমদানি করুন, একটি সাধারণ বেঞ্চমার্ক অ্যাপ, আপনার প্রকল্পে৷

আমরা todomvc ফোল্ডারের রেফারেন্স কোড জিপে TodoMVC অ্যাপের একটি সংস্করণ অন্তর্ভুক্ত করেছি। আপনার প্রোজেক্ট ফোল্ডারে todomvc থেকে সমস্ত ফাইল (ফোল্ডার সহ) কপি করুন।

কোডল্যাব ফোল্ডারে todomvc ফোল্ডারটি অনুলিপি করুন

আপনাকে index.html প্রতিস্থাপন করতে বলা হবে। এগিয়ে যান এবং গ্রহণ করুন.

index.html প্রতিস্থাপন করুন

আপনার এখন আপনার অ্যাপ্লিকেশন ফোল্ডারে নিম্নলিখিত ফাইল কাঠামো থাকা উচিত:

নতুন প্রকল্প ফোল্ডার

নীল রঙে হাইলাইট করা ফাইলগুলি todomvc ফোল্ডার থেকে।

এখন আপনার অ্যাপটি পুনরায় লোড করুন ( রাইট-ক্লিক করুন > অ্যাপ পুনরায় লোড করুন )। আপনি মৌলিক UI দেখতে হবে কিন্তু আপনি todos যোগ করতে সক্ষম হবে না.

স্ক্রিপ্ট বিষয়বস্তু নিরাপত্তা নীতি (CSP) অনুগত করুন

DevTools কনসোল খুলুন ( ডান-ক্লিক করুন > উপাদান পরিদর্শন করুন , তারপর কনসোল ট্যাব নির্বাচন করুন)। আপনি একটি ইনলাইন স্ক্রিপ্ট কার্যকর করতে অস্বীকার করার বিষয়ে একটি ত্রুটি দেখতে পাবেন:

CSP কনসোল লগ ত্রুটি সহ Todo অ্যাপ

অ্যাপটিকে বিষয়বস্তু নিরাপত্তা নীতি মেনে চলার মাধ্যমে এই ত্রুটিটি ঠিক করা যাক৷ সবচেয়ে সাধারণ CSP অ-সম্মতিগুলির মধ্যে একটি হল ইনলাইন জাভাস্ক্রিপ্টের কারণে। ইনলাইন জাভাস্ক্রিপ্টের উদাহরণগুলির মধ্যে ইভেন্ট হ্যান্ডলারগুলিকে DOM অ্যাট্রিবিউট হিসেবে অন্তর্ভুক্ত করে (যেমন <button onclick=''> ) এবং HTML-এর ভিতরে সামগ্রী সহ <script> ট্যাগ।

সমাধানটি সহজ: একটি নতুন ফাইলে ইনলাইন সামগ্রী সরান৷

1. index.html এর নীচে, ইনলাইন জাভাস্ক্রিপ্ট সরান এবং পরিবর্তে 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. bootstrap.js নামে js ফোল্ডারে একটি ফাইল তৈরি করুন। এই ফাইলে থাকা পূর্বের ইনলাইন কোডটি সরান:

// Bootstrap app data
window.app = {};

আপনি যদি এখন অ্যাপটি আবার লোড করেন কিন্তু আপনি কাছাকাছি চলে যাচ্ছেন তাহলে আপনার কাছে এখনও একটি কাজ না করা টোডো অ্যাপ থাকবে।

localStorage কে chrome.storage.local এ রূপান্তর করুন

আপনি এখন DevTools কনসোল খুললে, আগের ত্রুটিটি চলে যাবে। তবে window.localStorage উপলব্ধ না হওয়ার বিষয়ে একটি নতুন ত্রুটি রয়েছে:

স্থানীয় স্টোরেজ কনসোল লগ ত্রুটি সহ টোডো অ্যাপ

localStorage সিঙ্ক্রোনাস হওয়ায় Chrome অ্যাপ localStorage সমর্থন করে না। একক-থ্রেডেড রানটাইমে ব্লকিং রিসোর্স (I/O) এর সিঙ্ক্রোনাস অ্যাক্সেস আপনার অ্যাপটিকে প্রতিক্রিয়াহীন করে তুলতে পারে।

Chrome অ্যাপ্লিকেশানগুলির একটি সমতুল্য API রয়েছে যা বস্তুগুলিকে অ্যাসিঙ্ক্রোনাসভাবে সংরক্ষণ করতে পারে৷ এটি কখনও কখনও ব্যয়বহুল বস্তু->স্ট্রিং->অবজেক্ট সিরিয়ালাইজেশন প্রক্রিয়া এড়াতে সাহায্য করবে।

আমাদের অ্যাপে ত্রুটির বার্তাটি সমাধান করতে, আপনাকে localStorage কে chrome.storage.local এ রূপান্তর করতে হবে।

অ্যাপের অনুমতি আপডেট করুন

chrome.storage.local ব্যবহার করার জন্য, আপনাকে storage অনুমতির অনুরোধ করতে হবে। manifest.json এ, permissions অ্যারেতে "storage" যোগ করুন:

"permissions": ["storage"],

local.storage.set() এবং local.storage.get() সম্পর্কে জানুন

করণীয় আইটেমগুলি সংরক্ষণ এবং পুনরুদ্ধার করতে, আপনাকে chrome.storage API-এর set() এবং get() পদ্ধতি সম্পর্কে জানতে হবে।

সেট() পদ্ধতিটি কী-মানের জোড়ার একটি বস্তুকে তার প্রথম প্যারামিটার হিসাবে গ্রহণ করে। একটি ঐচ্ছিক কলব্যাক ফাংশন হল দ্বিতীয় প্যারামিটার। উদাহরণ স্বরূপ:

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

আপনি যদি বর্তমানে chrome.storage.local এ থাকা সমস্ত কিছু get() চান তবে প্রথম প্যারামিটারটি বাদ দিন:

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

localStorage এর বিপরীতে, আপনি DevTools রিসোর্স প্যানেল ব্যবহার করে স্থানীয়ভাবে সঞ্চিত আইটেমগুলি পরিদর্শন করতে পারবেন না৷ যাইহোক, আপনি জাভাস্ক্রিপ্ট কনসোল থেকে chrome.storage এর সাথে যোগাযোগ করতে পারেন:

chrome.storage ডিবাগ করতে কনসোল ব্যবহার করুন

প্রিভিউ প্রয়োজনীয় API পরিবর্তন

টোডো অ্যাপ রূপান্তর করার বাকি ধাপগুলির বেশিরভাগই হল API কলগুলিতে ছোট পরিবর্তন। localStorage বর্তমানে ব্যবহৃত হচ্ছে এমন সমস্ত স্থান পরিবর্তন করা, যদিও সময়সাপেক্ষ এবং ত্রুটি-প্রবণ, প্রয়োজন।

localStorage এবং chrome.storage এর মধ্যে মূল পার্থক্যগুলি chrome.storage এর async প্রকৃতি থেকে আসে:

  • সাধারণ অ্যাসাইনমেন্ট ব্যবহার করে 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 Store this বোঝায়। (বাউন্ড ফাংশন সম্পর্কে আরও তথ্য 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();
    

এই মূল পার্থক্যগুলি মাথায় রাখুন কারণ আমরা নিম্নলিখিত বিভাগে করণীয় আইটেমগুলি পুনরুদ্ধার, সংরক্ষণ এবং অপসারণ কভার করি।

করণীয় আইটেম পুনরুদ্ধার করুন

টোডো আইটেমগুলি পুনরুদ্ধার করার জন্য টোডো অ্যাপটি আপডেট করা যাক:

1. Store কনস্ট্রাক্টর পদ্ধতি ডেটাস্টোর থেকে বিদ্যমান সমস্ত টোডো আইটেমগুলির সাথে টোডো অ্যাপ শুরু করার যত্ন নেয়। পদ্ধতিটি প্রথমে চেক করে যে ডেটাস্টোর বিদ্যমান কিনা। যদি এটি না হয়, তাহলে এটি একটি খালি todos তৈরি করবে এবং ডেটাস্টোরে সংরক্ষণ করবে যাতে রানটাইম পড়ার কোনো ত্রুটি না থাকে।

js/store.js- এ, chrome.storage.local ব্যবহার করার পরিবর্তে কনস্ট্রাক্টর পদ্ধতিতে localStorage এর ব্যবহার রূপান্তর করুন:

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() পদ্ধতি ব্যবহার করা হয়। আপনি "সমস্ত", "সক্রিয়" বা "সম্পূর্ণ" দ্বারা ফিল্টার করছেন কিনা তার উপর ভিত্তি করে প্রত্যাবর্তিত ফলাফল পরিবর্তিত হয়।

chrome.storage.local ব্যবহার করতে find() রূপান্তর করুন:

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() মডেল থেকে সমস্ত টোডো পায়। chrome.storage.local ব্যবহার করতে findAll() রূপান্তর করুন:

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

Todos আইটেম সংরক্ষণ করুন

বর্তমান save() পদ্ধতি একটি চ্যালেঞ্জ উপস্থাপন করে। এটি দুটি অ্যাসিঙ্ক ক্রিয়াকলাপ (গেট এবং সেট) এর উপর নির্ভর করে যা প্রতিবার পুরো একচেটিয়া JSON স্টোরেজে কাজ করে। একাধিক করণীয় আইটেমের যেকোনো ব্যাচের আপডেট, যেমন "সমস্ত টোডোসকে সম্পূর্ণ হিসাবে চিহ্নিত করুন" এর ফলে একটি ডেটা বিপদ হবে যা Read-After-Write নামে পরিচিত। আমরা যদি IndexedDB-এর মতো আরও উপযুক্ত ডেটা স্টোরেজ ব্যবহার করি তবে এই সমস্যাটি ঘটবে না, তবে আমরা এই কোডল্যাবের জন্য রূপান্তর প্রচেষ্টা কমানোর চেষ্টা করছি।

এটি ঠিক করার বিভিন্ন উপায় রয়েছে তাই আমরা এই সুযোগটি ব্যবহার করব সামান্য রিফ্যাক্টর save() করার জন্য টুডো আইডিগুলির একটি অ্যারে নিয়ে যা একবারে আপডেট করা হবে:

1. শুরু করতে, একটি chrome.storage.local.get() কলব্যাক দিয়ে save() এর ভিতরে ইতিমধ্যেই সবকিছু মুড়ে দিন:

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 দিয়ে সমস্ত localStorage ইনস্ট্যান্স রূপান্তর করুন:

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() কল করতে শুধুমাত্র একবার todos এর অ্যারের সাথে একটি টোডোকে একে একে সম্পূর্ণ হিসাবে চিহ্নিত করার পরিবর্তে। এছাড়াও _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:

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

সমস্ত করণীয় আইটেম ড্রপ

localStorage ব্যবহার করে store.js এ আরও একটি পদ্ধতি রয়েছে:

Store.prototype.drop = function (callback) {
  localStorage[this._dbName] = JSON.stringify({todos: []});
  callback.call(this, JSON.parse(localStorage[this._dbName]).todos);
};

এই পদ্ধতিটি বর্তমান অ্যাপে বলা হচ্ছে না তাই, আপনি যদি একটি অতিরিক্ত চ্যালেঞ্জ চান, তাহলে নিজে থেকে এটি প্রয়োগ করার চেষ্টা করুন। ইঙ্গিত: chrome.storage.local.clear() এ দেখুন।

আপনার সমাপ্ত টোডো অ্যাপ চালু করুন

আপনি ধাপ 2 সম্পন্ন! আপনার অ্যাপটি পুনরায় লোড করুন এবং আপনার কাছে এখন TodoMVC-এর একটি সম্পূর্ণরূপে কাজ করা Chrome প্যাকেজ সংস্করণ থাকা উচিত৷

আরও তথ্যের জন্য

এই ধাপে প্রবর্তিত কিছু API সম্পর্কে আরও বিস্তারিত তথ্যের জন্য, পড়ুন:

পরবর্তী ধাপে চালিয়ে যেতে প্রস্তুত? ধাপ 3 এ যান - অ্যালার্ম এবং বিজ্ঞপ্তি যোগ করুন »

,

এই ধাপে, আপনি শিখবেন:

  • Chrome Apps প্ল্যাটফর্মের জন্য একটি বিদ্যমান ওয়েব অ্যাপ্লিকেশন কীভাবে মানিয়ে নেওয়া যায়।
  • কিভাবে আপনার অ্যাপ স্ক্রিপ্ট কন্টেন্ট সিকিউরিটি পলিসি (CSP) অনুগত করবেন।
  • chrome.storage.local ব্যবহার করে কিভাবে স্থানীয় স্টোরেজ বাস্তবায়ন করবেন।

এই ধাপটি সম্পূর্ণ করার আনুমানিক সময়: 20 মিনিট।
এই ধাপে আপনি যা সম্পূর্ণ করবেন তার পূর্বরূপ দেখতে, এই পৃষ্ঠার নীচে ঝাঁপ দাও ↓

একটি বিদ্যমান Todo অ্যাপ আমদানি করুন

একটি সূচনা বিন্দু হিসাবে, TodoMVC- এর ভ্যানিলা জাভাস্ক্রিপ্ট সংস্করণ আমদানি করুন, একটি সাধারণ বেঞ্চমার্ক অ্যাপ, আপনার প্রকল্পে৷

আমরা todomvc ফোল্ডারের রেফারেন্স কোড জিপে TodoMVC অ্যাপের একটি সংস্করণ অন্তর্ভুক্ত করেছি। আপনার প্রোজেক্ট ফোল্ডারে todomvc থেকে সমস্ত ফাইল (ফোল্ডার সহ) কপি করুন।

কোডল্যাব ফোল্ডারে todomvc ফোল্ডারটি অনুলিপি করুন

আপনাকে index.html প্রতিস্থাপন করতে বলা হবে। এগিয়ে যান এবং গ্রহণ করুন.

index.html প্রতিস্থাপন করুন

আপনার এখন আপনার অ্যাপ্লিকেশন ফোল্ডারে নিম্নলিখিত ফাইল কাঠামো থাকা উচিত:

নতুন প্রকল্প ফোল্ডার

নীল রঙে হাইলাইট করা ফাইলগুলি todomvc ফোল্ডার থেকে।

এখন আপনার অ্যাপটি পুনরায় লোড করুন ( রাইট-ক্লিক করুন > অ্যাপ পুনরায় লোড করুন )। আপনি মৌলিক UI দেখতে হবে কিন্তু আপনি todos যোগ করতে সক্ষম হবে না.

স্ক্রিপ্ট বিষয়বস্তু নিরাপত্তা নীতি (CSP) অনুগত করুন

DevTools কনসোল খুলুন ( ডান-ক্লিক করুন > উপাদান পরিদর্শন করুন , তারপর কনসোল ট্যাব নির্বাচন করুন)। আপনি একটি ইনলাইন স্ক্রিপ্ট কার্যকর করতে অস্বীকার করার বিষয়ে একটি ত্রুটি দেখতে পাবেন:

CSP কনসোল লগ ত্রুটি সহ Todo অ্যাপ

অ্যাপটিকে বিষয়বস্তু নিরাপত্তা নীতি মেনে চলার মাধ্যমে এই ত্রুটিটি ঠিক করা যাক৷ সবচেয়ে সাধারণ CSP অ-সম্মতিগুলির মধ্যে একটি হল ইনলাইন জাভাস্ক্রিপ্টের কারণে। ইনলাইন জাভাস্ক্রিপ্টের উদাহরণগুলির মধ্যে ইভেন্ট হ্যান্ডলারগুলিকে DOM অ্যাট্রিবিউট হিসেবে অন্তর্ভুক্ত করে (যেমন <button onclick=''> ) এবং HTML-এর ভিতরে সামগ্রী সহ <script> ট্যাগ।

সমাধানটি সহজ: একটি নতুন ফাইলে ইনলাইন সামগ্রী সরান৷

1. index.html এর নীচে, ইনলাইন জাভাস্ক্রিপ্ট সরান এবং পরিবর্তে 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. bootstrap.js নামে js ফোল্ডারে একটি ফাইল তৈরি করুন। এই ফাইলে থাকা পূর্বের ইনলাইন কোডটি সরান:

// Bootstrap app data
window.app = {};

আপনি যদি এখন অ্যাপটি আবার লোড করেন কিন্তু আপনি কাছাকাছি চলে যাচ্ছেন তাহলে আপনার কাছে এখনও একটি কাজ না করা টোডো অ্যাপ থাকবে।

localStorage কে chrome.storage.local এ রূপান্তর করুন

আপনি এখন DevTools কনসোল খুললে, আগের ত্রুটিটি চলে যাবে। তবে window.localStorage উপলব্ধ না হওয়ার বিষয়ে একটি নতুন ত্রুটি রয়েছে:

স্থানীয় স্টোরেজ কনসোল লগ ত্রুটি সহ টোডো অ্যাপ

localStorage সিঙ্ক্রোনাস হওয়ায় Chrome অ্যাপ localStorage সমর্থন করে না। একক-থ্রেডেড রানটাইমে ব্লকিং রিসোর্স (I/O) এর সিঙ্ক্রোনাস অ্যাক্সেস আপনার অ্যাপটিকে প্রতিক্রিয়াহীন করে তুলতে পারে।

Chrome অ্যাপ্লিকেশানগুলির একটি সমতুল্য API রয়েছে যা বস্তুগুলিকে অ্যাসিঙ্ক্রোনাসভাবে সংরক্ষণ করতে পারে৷ এটি কখনও কখনও ব্যয়বহুল বস্তু->স্ট্রিং->অবজেক্ট সিরিয়ালাইজেশন প্রক্রিয়া এড়াতে সাহায্য করবে।

আমাদের অ্যাপে ত্রুটির বার্তাটি সমাধান করতে, আপনাকে localStorage কে chrome.storage.local এ রূপান্তর করতে হবে।

অ্যাপের অনুমতি আপডেট করুন

chrome.storage.local ব্যবহার করার জন্য, আপনাকে storage অনুমতির অনুরোধ করতে হবে। manifest.json এ, permissions অ্যারেতে "storage" যোগ করুন:

"permissions": ["storage"],

local.storage.set() এবং local.storage.get() সম্পর্কে জানুন

করণীয় আইটেমগুলি সংরক্ষণ এবং পুনরুদ্ধার করতে, আপনাকে chrome.storage API-এর set() এবং get() পদ্ধতি সম্পর্কে জানতে হবে।

সেট() পদ্ধতিটি কী-মানের জোড়ার একটি বস্তুকে তার প্রথম প্যারামিটার হিসাবে গ্রহণ করে। একটি ঐচ্ছিক কলব্যাক ফাংশন হল দ্বিতীয় প্যারামিটার। উদাহরণ স্বরূপ:

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

আপনি যদি বর্তমানে chrome.storage.local এ থাকা সমস্ত কিছু get() চান তবে প্রথম প্যারামিটারটি বাদ দিন:

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

localStorage এর বিপরীতে, আপনি DevTools রিসোর্স প্যানেল ব্যবহার করে স্থানীয়ভাবে সঞ্চিত আইটেমগুলি পরিদর্শন করতে পারবেন না৷ যাইহোক, আপনি জাভাস্ক্রিপ্ট কনসোল থেকে chrome.storage এর সাথে যোগাযোগ করতে পারেন:

chrome.storage ডিবাগ করতে কনসোল ব্যবহার করুন

প্রিভিউ প্রয়োজনীয় API পরিবর্তন

টোডো অ্যাপ রূপান্তর করার বাকি ধাপগুলির বেশিরভাগই হল API কলগুলিতে ছোট পরিবর্তন। localStorage বর্তমানে ব্যবহৃত হচ্ছে এমন সমস্ত স্থান পরিবর্তন করা, যদিও সময়সাপেক্ষ এবং ত্রুটি-প্রবণ, প্রয়োজন।

localStorage এবং chrome.storage এর মধ্যে মূল পার্থক্যগুলি chrome.storage এর async প্রকৃতি থেকে আসে:

  • সাধারণ অ্যাসাইনমেন্ট ব্যবহার করে 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 Store this বোঝায়। (বাউন্ড ফাংশন সম্পর্কে আরও তথ্য 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();
    

এই মূল পার্থক্যগুলি মাথায় রাখুন কারণ আমরা নিম্নলিখিত বিভাগে করণীয় আইটেমগুলি পুনরুদ্ধার, সংরক্ষণ এবং অপসারণ কভার করি।

করণীয় আইটেম পুনরুদ্ধার করুন

টোডো আইটেমগুলি পুনরুদ্ধার করার জন্য টোডো অ্যাপটি আপডেট করা যাক:

1. Store কনস্ট্রাক্টর পদ্ধতি ডেটাস্টোর থেকে বিদ্যমান সমস্ত টোডো আইটেমগুলির সাথে টোডো অ্যাপ শুরু করার যত্ন নেয়। পদ্ধতিটি প্রথমে চেক করে যে ডেটাস্টোর বিদ্যমান কিনা। যদি এটি না হয়, তাহলে এটি একটি খালি todos তৈরি করবে এবং ডেটাস্টোরে সংরক্ষণ করবে যাতে রানটাইম পড়ার কোনো ত্রুটি না থাকে।

js/store.js- এ, chrome.storage.local ব্যবহার করার পরিবর্তে কনস্ট্রাক্টর পদ্ধতিতে localStorage এর ব্যবহার রূপান্তর করুন:

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() পদ্ধতি ব্যবহার করা হয়। আপনি "সমস্ত", "সক্রিয়" বা "সম্পূর্ণ" দ্বারা ফিল্টার করছেন কিনা তার উপর ভিত্তি করে প্রত্যাবর্তিত ফলাফল পরিবর্তিত হয়।

chrome.storage.local ব্যবহার করতে find() রূপান্তর করুন:

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() মডেল থেকে সমস্ত টোডো পায়। chrome.storage.local ব্যবহার করতে findAll() রূপান্তর করুন:

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

Todos আইটেম সংরক্ষণ করুন

বর্তমান save() পদ্ধতি একটি চ্যালেঞ্জ উপস্থাপন করে। এটি দুটি অ্যাসিঙ্ক ক্রিয়াকলাপ (গেট এবং সেট) এর উপর নির্ভর করে যা প্রতিবার পুরো একচেটিয়া JSON স্টোরেজে কাজ করে। একাধিক করণীয় আইটেমের যেকোনো ব্যাচের আপডেট, যেমন "সমস্ত টোডোসকে সম্পূর্ণ হিসাবে চিহ্নিত করুন" এর ফলে একটি ডেটা বিপদ হবে যা Read-After-Write নামে পরিচিত। আমরা যদি IndexedDB-এর মতো আরও উপযুক্ত ডেটা স্টোরেজ ব্যবহার করি তবে এই সমস্যাটি ঘটবে না, তবে আমরা এই কোডল্যাবের জন্য রূপান্তর প্রচেষ্টা কমানোর চেষ্টা করছি।

এটি ঠিক করার বিভিন্ন উপায় রয়েছে তাই আমরা এই সুযোগটি ব্যবহার করব সামান্য রিফ্যাক্টর save() করার জন্য টুডো আইডিগুলির একটি অ্যারে নিয়ে যা একবারে আপডেট করা হবে:

1. শুরু করতে, একটি chrome.storage.local.get() কলব্যাক দিয়ে save() এর ভিতরে ইতিমধ্যেই সবকিছু মুড়ে দিন:

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 দিয়ে সমস্ত localStorage ইনস্ট্যান্স রূপান্তর করুন:

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() কল করতে শুধুমাত্র একবার todos এর অ্যারের সাথে একটি টোডোকে একে একে সম্পূর্ণ হিসাবে চিহ্নিত করার পরিবর্তে। এছাড়াও _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:

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

সমস্ত করণীয় আইটেম ড্রপ

localStorage ব্যবহার করে store.js এ আরও একটি পদ্ধতি রয়েছে:

Store.prototype.drop = function (callback) {
  localStorage[this._dbName] = JSON.stringify({todos: []});
  callback.call(this, JSON.parse(localStorage[this._dbName]).todos);
};

এই পদ্ধতিটি বর্তমান অ্যাপে বলা হচ্ছে না তাই, আপনি যদি একটি অতিরিক্ত চ্যালেঞ্জ চান, তাহলে নিজে থেকে এটি প্রয়োগ করার চেষ্টা করুন। ইঙ্গিত: chrome.storage.local.clear() এ দেখুন।

আপনার সমাপ্ত টোডো অ্যাপ চালু করুন

আপনি ধাপ 2 সম্পন্ন! আপনার অ্যাপটি পুনরায় লোড করুন এবং আপনার কাছে এখন TodoMVC-এর একটি সম্পূর্ণরূপে কাজ করা Chrome প্যাকেজ সংস্করণ থাকা উচিত৷

আরও তথ্যের জন্য

এই ধাপে প্রবর্তিত কিছু API সম্পর্কে আরও বিস্তারিত তথ্যের জন্য, পড়ুন:

পরবর্তী ধাপে চালিয়ে যেতে প্রস্তুত? ধাপ 3 এ যান - অ্যালার্ম এবং বিজ্ঞপ্তি যোগ করুন »