Saving an object to Chrome storage, but getting different results back on retrieval - javascript

I'm using an object with information for a bookmark tagging system that needs to persist across Chrome sessions, so I'm trying to save it to local storage and update it whenever a new bookmark is created.
When I create a new bookmark, I fire a function to see if there are now any other bookmarks with the same tag as the new bookmark. This organizes bookmarks into "tag groups" that function kind of like dynamic folders.
When I set the storage object, the object being stored has all the data I'd expect. However, as soon as I get the same object out of storage, one of the nested objects mysteriously turns to null. See console output: the top object is just before the set call in function updateStorage. The bottom is what I get back when I "get" that object from storage. Notice the tagGroups bookmarks are now null. The bookmarks themselves are still there, it's only in the tag group object that they disappear. I've spent a full day and night messing around with this trying to get it to work.
Here is the model code. I included everything for context, but the most relevant pieces are the createNewBookmark, updatePrimaryTreeWithTagGroups, and updateStorage methods.
UPDATE: I've edited the code to make all the changes to the bookmarks tree before setting/getting anything from storage, then making a final call to update storage with the resulting object. I'm literally storing one thing, one time, and getting back another whenever I try to retrieve.
function PrimaryBookmarksTree(){
chrome.storage.sync.get(null, this.findOrCreate.bind(this));
}
PrimaryBookmarksTree.prototype.findOrCreate = function(result){
if (result.bookmarksTree != undefined){
this.bookmarks = result.bookmarksTree.bookmarks;
this.title = result.bookmarksTree.title;
this.tagGroups = result.bookmarksTree.tagGroups;
console.log(this);
} else {
this.bookmarks = [];
this.title = "Marinade Bookmarks";
this.tagGroups = [];
chrome.storage.sync.set({"bookmarksTree": this}, function(){console.log("New tree created!")});
console.log(this);
}
}
function Bookmark(name, tags, url){
this.name = name;
this.tags = tags;
this.url = url;
this.dateCreated = this.date();
}
function TagGroup(tag){
this.bookmarks = [];
this.tag = tag;
}
//called by controller when user tags a new bookmark via the extension
PrimaryBookmarksTree.prototype.createNewBookmark = function(name, tags, url){
var newBookmark = new Bookmark(name, tags, url);
this.bookmarks.push(newBookmark);
this.tagGroups = this.updatePrimaryTreeWithTagGroups();
this.updateStorage(this);
}
PrimaryBookmarksTree.prototype.updatePrimaryTreeWithTagGroups = function(){
var tagsForGrouping = this.getTagsWithMultipleBookmarks(this.bookmarks);
for(j=0;j<tagsForGrouping.length;j++){
this.tagGroups.push(this.buildTagGroup(tagsForGrouping[j]));
}
return this.tagGroups;
}
PrimaryBookmarksTree.prototype.getTagsWithMultipleBookmarks = function(bookmarks){
var tagsToCheck = this.pluck(bookmarks, "tags");
var tagCounts = tagsToCheck.reduce(function (obj, curr){
if (typeof obj[curr] == 'undefined') {
obj[curr] = 1;
} else {
obj[curr] += 1;
}
return obj;
}, {});
var tagGroups = this.filter(tagCounts, function(x){return x > 1});
return tagGroups;
}
PrimaryBookmarksTree.prototype.buildTagGroup = function(tag){
tagGroup = new TagGroup(tag);
for(i=0;i<this.bookmarks.length;i++){
if(this.bookmarks[i].tags[0] == tag){
tagGroup.bookmarks.push(this.bookmarks[i]);
}
}
if (tagGroup.bookmarks.length != 0){
return tagGroup;
}
}
PrimaryBookmarksTree.prototype.updateStorage = function(updatedTree){
console.log(JSON.stringify(updatedTree));
chrome.storage.sync.set({"bookmarksTree": updatedTree}, function(){console.log("final storage complete")});
}

You are always setting this.tagGroups to undefined when you retrieve your data from the sync storage:
PrimaryBookmarksTree.prototype.findOrCreate = function(result){
if (result.bookmarksTree != undefined){
this.bookmarks = result.bookmarksTree.bookmarks;
this.title = result.bookmarksTree.title;
this.tagGroups = result.tagGroups; // should be result.bookmarksTree.tagGroups
console.log(this);
}
}

Related

Get specifics ids in IndexedDB [duplicate]

I want to execute this query
select * from properties where propertyCode IN ("field1", "field2", "field3")
How can I achieve this in IndexedDB
I tried this thing
getData : function (indexName, params, objectStoreName) {
var defer = $q.defer(),
db, transaction, index, cursorRequest, request, objectStore, resultSet, dataList = [];
request = indexedDB.open('test');
request.onsuccess = function (event) {
db = request.result;
transaction = db.transaction(objectStoreName);
objectStore = transaction.objectStore(objectStoreName);
index = objectStore.index(indexName);
cursorRequest = index.openCursor(IDBKeyRange.only(params));
cursorRequest.onsuccess = function () {
resultSet = cursorRequest.result;
if(resultSet){
dataList.push(resultSet.value);
resultSet.continue();
}
else{
console.log(dataList);
defer.resolve(dataList);
}
};
cursorRequest.onerror = function (event) {
console.log('Error while opening cursor');
}
}
request.onerror = function (event) {
console.log('Not able to get access to DB in executeQuery');
}
return defer.promise;
But didn't worked. I tried google but couldn't find exact answer.
If you consider that IN is essentially equivalent to field1 == propertyCode OR field2 == propertyCode, then you could say that IN is just another way of using OR.
IndexedDB cannot do OR (unions) from a single request.
Generally, your only recourse is to do separate requests, then merge them in memory. Generally, this will not have great performance. If you are dealing with a lot of objects, you might want to consider giving up altogether on this approach and thinking of how to avoid such an approach.
Another approach is to iterate over all objects in memory, and then filter those that don't meet your conditions. Again, terrible performance.
Here is a gimmicky hack that might give you decent performance, but it requires some extra work and a tiny bit of storage overhead:
Store an extra field in your objects. For example, plan to use a property named hasPropertyCodeX.
Whenever any of the 3 properties are true (has the right code), set the field (as in, just make it a property of the object, its value is irrelevant).
When none of the 3 properties are true, delete the property from the object.
Whenever the object is modified, update the derived property (set or unset it as appropriate).
Create an index on this derived property in indexedDB.
Open a cursor over the index. Only objects with a property present will appear in the cursor results.
Example for 3rd approach
var request = indexedDB.open(...);
request.onupgradeneeded = upgrade;
function upgrade(event) {
var db = event.target.result;
var store = db.createObjectStore('store', ...);
// Create another index for the special property
var index = store.createIndex('hasPropCodeX', 'hasPropCodeX');
}
function putThing(db, thing) {
// Before storing the thing, secretly update the hasPropCodeX value
// which is derived from the thing's other properties
if(thing.field1 === 'propCode' || thing.field2 === 'propCode' ||
thing.field3 === 'propCode') {
thing.hasPropCodeX = 1;
} else {
delete thing.hasPropCodeX;
}
var tx = db.transaction('store', 'readwrite');
var store = tx.objectStore('store');
store.put(thing);
}
function getThingsWherePropCodeXInAnyof3Fields(db, callback) {
var things = [];
var tx = db.transaction('store');
var store = tx.objectStore('store');
var index = store.index('hasPropCodeX');
var request = index.openCursor();
request.onsuccess = function(event) {
var cursor = event.target.result;
if(cursor) {
var thing = cursor.value;
things.push(thing);
cursor.continue();
} else {
callback(things);
}
};
request.onerror = function(event) {
console.error(event.target.error);
callback(things);
};
}
// Now that you have an api, here is some example calling code
// Not bothering to promisify it
function getData() {
var request = indexedDB.open(...);
request.onsuccess = function(event) {
var db = event.target.result;
getThingsWherePropCodeXInAnyof3Fields(db, function(things) {
console.log('Got %s things', things.length);
for(let thing of things) {
console.log('Thing', thing);
}
});
};
}

Storing items as cookies, works perfectly fine on a normal browser, but on a native device browser there are implications

I am creating a website for a client at the moment, we decided an easy way to store "items" which will be passed down to a subdomain from the root would be to store them as cookies. This works perfectly fine in a normal browser, yet when I tested it on a native device browser it didn't work as smoothly. I am wondering where some of these problems may have been coming from and hoping you wonderful developers can lend a man a hand.
The idea is that on the frontend when a "Your Order" side drawer is pressed, a function runs grabbing the cookies and then sorts them into their specified content area's -> Downloadable Content, Requested Material and Bespoke Content. I have created two separate functions for this, one that was the original working piece and another more tailored and "good practice".
Tried having the "Value" of the cookie containing the values that need to be stored such as, [itemname],[itemlocation], [itemdescription], [itemtype].
The second function stores the item data in an object, the object is then JSON.stringified and iterated over in a for loop. This is then taken out of a string with JSON.parse() and further iterated over in an .each() iterating over the index(key) and val(value).
FIRST FUNCTION:
$('section#review-downloads a.toggle-btn').bind('click tap', function() {
let cookies;
let itemSplit;
var section = $('section#review-downloads');
if(section.hasClass('active')) {
section.removeClass('active');
setTimeout(function() {
$('section#review-downloads .selected-items div').find('p').remove();
}, 900);
} else {
section.addClass('active');
$.each(document.cookie.split(';'), function() {
cookies = this.split('=');
let trimId = cookies[0].trim();
vals = cookies[1].replace(/[\])}[{(]/g, '');
if(!(cookies[0] === "envFilter")) {
$.each(vals.split('[ ]'),function() {
itemSplit = this.split(',');
let itemId = trimId;
let itemName = itemSplit[0];
let itemUrl = itemSplit[1];
let itemType = itemSplit[2];
let itemDesc = itemSplit[3];
if(itemType === ' Downloadable Content ') {
$('<p id="selected-item-'+itemId+'"><strong>'+itemName+'</strong>'+itemDesc+'</p>').appendTo('section#review-downloads .review-container .selected-items .downloadable-content');
} else if (itemType === ' Requested Materials ') {
$('<p id="selected-item"><strong>'+itemName+'</strong>'+itemDesc+'</p>').appendTo('section#review-downloads .review-container .selected-items .requested-material');
} else if (itemType === ' Bespoke Content ') {
$('<p id="selected-item"><strong>'+itemName+'</strong>'+itemDesc+'</p>').appendTo('section#review-downloads .review-container .selected-items .bespoke-content');
}
});
};
});
}
return false;
});
THE SECOND FUNCTION (best practice)
$('div.support-item-wrapper div.order-add').bind('click tap', function() {
let id = $(this).data('id');
let name = $(this).data('title');
let file = $(this).data('file');
let type = $(this).data('type');
let desc = $(this).data('description').replace(/(\r\n|\n|\r)/gm, "");
let url = $(this).data('url');
let cookieVal = {
name: name,
file: file,
type: type,
desc: desc,
url: url
};
let string = JSON.stringify(cookieVal);
setCookie('product-'+id, string, 1);
});
$('section#review-downloads a.toggle-btn').bind('click tap', function() {
var section = $('section#review-downloads');
if(section.hasClass('active')) {
section.removeClass('active');
} else {
section.addClass('active');
let decoded_user_product;
cookie_values = document.cookie.split(';');
for(i = 0; i < cookie_values.length; i++) {
cookie_split = cookie_values[i].split("=");
cookie_key = cookie_split[0].trim();
cookie_value = cookie_split[1].trim();
// console.log(cookie_value);
if(cookie_key != "envFilter") {
decoded_user_product = JSON.parse(cookie_value);
}
$.each(decoded_user_product, function(index, val) {
// console.log("index:" + index + "& val:" + val);
if(index === "name") {
console.log(val);
} else if (index === "type") {
console.log(val);
} else if (index === "desc") {
console.log(val);
}
});
}
// console.log(decoded_user_product);
};
});
On Desktop, the results are perfectly fine. Each item is easily console.log()'able and has been easily sorted in the FIRST FUNCTION.
On Mobile, the same results were as to be expected. But after realising it hadn't worked I used chrome://inspect along with a lot of console.logs to come to the conclusion that I may be too inexperienced to understand what parts of my code are unable to run on a native browser.

Saving data in localstorage

I'm try to save data to localstorage. Created "class" through constructor and try to put get and set methods to them. But when I click my button(when button was clicked data must be saving) nothing happens(In developer tools "Resource" tab). When I tried simple save data through JSON.stringify and else all was worked.
(function() {
window.onload = function() {
document.getElementById('buttonCreate').onclick = function() {
var topicValue = document.getElementById("create-topic").value;
var statusValue = document.getElementById("create-status").value;
var descriptionValue = document.getElementById("create-description").value;
var storage = new Storage();
var ticket = {
topic: topicValue,
status: statusValue,
description: descriptionValue
};
storage.set("Item", item);
}
}
})();
"class" Storage:
function Storage() {
this._ITEMS_DESCRIPTOR = 'items';
}
Storage.prototype.get = function() {
var fromStorage = localStorage.getItem(this._ITEMS_DESCRIPTOR);
return fromStorage ? JSON.parse(fromStorage) : [];
};
Storage.prototype.set = function(key, items) {
localStorage.setItem(key, JSON.stringify(items));
};
The exact issue with your code is the storage key and also the item that you are trying to store which is not defined.
It stores it in to Item key and the get method is written to take it from the key, items.
by looking at the given code, you should suppose to store ticket object. storage.set(ticket);
A suggested better approach: To pass a key to instantiate Storage object and then use it accordingly.
Such as var storage = new Storage('Item');
(function() {
window.onload = function() {
document.getElementById('buttonCreate').onclick = function() {
var topicValue = document.getElementById("create-topic").value;
var statusValue = document.getElementById("create-status").value;
var descriptionValue = document.getElementById("create-description").value;
var storage = new Storage("ticket");
var ticket = {
topic: topicValue,
status: statusValue,
description: descriptionValue
};
storage.set(ticket);
}
}
})();
"class" Storage:
function Storage(key) {
this._ITEMS_DESCRIPTOR = key;
}
Storage.prototype.get = function() {
var fromStorage = localStorage.getItem(this._ITEMS_DESCRIPTOR);
return fromStorage ? JSON.parse(fromStorage) : {};
};
Storage.prototype.set = function(item) {
localStorage.setItem(this._ITEMS_DESCRIPTOR, JSON.stringify(item));
};
To get the ticket value stored in localstorage:
var storage = new Storage('ticket');
var ticket = storage.get();
Per Mike McCaughan's comment, you were referencing an undefined variable.
Using strict mode would have caught this.
You have another bug in the keys used to address items in storage:
'items' !== 'Item'
Also; you have no classes in your code.

Chrome Extension Storing Custom Object Type Strips Prototype Methods

I have created a custom object that I am using in my extension. When I save objects of the type Group (my object type) and then later pull those objects out of storage, it appears that the prototype methods are no longer present. Now I read in the documentation that objects serialize down to object literals {} and I can't seem to figure out how to keep the methods with the objects. I have provided the code of the group class below. When I try and use one of the methods from the file below on an object that was retrieved from storage, I get an error that the function does not exist. I used a for in loop to loop through all of the properties and the object has the name and urls property. Any help would be greatly appreciated!
Group.js:
// Create the Group class
var Group = function (name, urls) {
this.name = name;
this.urls = urls;
};
// Clears all urls from the group
Group.prototype.clearUrls = function () {
this.urls = [];
};
// Adds the specified url to the group
Group.prototype.addUrl = function (url) {
this.urls.append(url);
};
// Removes the specified url from the group
Group.prototype.removeUrl = function (url) {
this.urls = this.urls.filter(function(_url){
return url !== _url;
});
};
// Renames the group
Group.prototype.rename = function (name) {
this.name = name;
};
// Checks whether or not the group contains the specified url
// Returns either true or false
Group.prototype.containsUrl = function (url) {
var contains = false;
for (var i = 0; i < this.urls.length; i++) {
if (this.urls[i] === url) {
contains = true;
break;
}
}
return contains;
};
EDIT:
Here is the background.js script, it shows how the object is retrieved and then how it is called later in the script. It fails when it receives the addUrl message and attempts to call containsUrl() on currentGroup.
// Global Variables
var currentGroup;
var groups = [];
var options = [];
// Short hand function to save the current data to storage
var saveData = function () {
// Save default options, currrentGroup, and groups
chrome.storage.sync.set({'options': options, 'currentGroup': currentGroup, 'groups': groups}, function() {
if (chrome.runtime.lastError) {
console.error("Could not save because: " + chrome.runtime.lastError);
}
});
}
// On start query for saved data to make sure data is current
chrome.storage.sync.get(function(items) {
// Check if there are groups
if (items['groups']) { // Set the groups
groups = items['groups'];
} else { // Create default group and add to list of groups
currentGroup = new Group('default', []);
groups = [currentGroup];
}
// Check for current group, if none set to first available group
if (items['currentGroup']) {
currentGroup = items['currentGroup'];
console.log(Object.getOwnPropertyNames(currentGroup));
} else {
currentGroup = groups[0];
}
// Check for the options
if (items['options']) {
options = items['options'];
} else {
// No options, set the default options and save them
options['overrideHomepages'] = true;
}
saveData();
// After data has been fetched bring up the tabs
chrome.tabs.query({'currentWindow': true}, function(tabs) {
for (var i = 0; i < currentGroup.urls.length; i++) {
if (options['overrideHomepages']) {
if (tabs[i].url.length > 0) {
chrome.tabs.update(tabs[0].id, {'url': currentGroup.urls[i]});
} else {
chrome.tabs.create({'url': currentGroup.urls[i]});
}
} else { // Don't override homepages or open tabs
chrome.tabs.create({'url': currentGroup.urls[i]});
}
currentGroup.urls[i]
}
}); // End tabs.query
}); // End storage.sync.get
// Add message listener
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
// If add url was sent
if (request.message === 'addUrl') {
console.log('Recieved message: ' + request.message);
// Check if the group contains the url already
if (currentGroup.containsUrl(sender.url) === false) {
currentGroup.addUrl(sender.url);
saveData();
sendResponse({'message': 'Saved ' + sender.url});
}
}
// If remove url was sent
if (request.message === 'removeUrl') {
// Check if the group contains the url
if (currentGroup.containsUrl(sender.url)) {
currentGroup.removeUrl(sender.url);
saveData();
sendResponse({'message': 'Removed ' + sender.url})
}
}
});
I believe currently chrome.storage is only used to save key-value items, not including prototype/functions. However, I didn't find any official docs about this.
One workaround would be using Group.prototype.containsUrl.call(currentGroup, sender.url), it allows you to invoke containsUrl with specifying the context for "this".

Storing arrays in localStorage error

I have a bug in my code that only saves the last object in an array upon reload. I have a feeling that my addAccount() function is not saving or inserting data correctly. Everything else works correctly. In my console, it shows that the data is being inserted into the array, but when I refresh I only get the last object saved.
I'm not sure what to do.
// The list of accounts array.
var accountsArray = [];
function addAccount() {
// Take fields and put user data into varables.
var accountName = document.getElementById('accountName').value;
var accountBalance = document.getElementById('accountBalance').value;
var accountType = document.getElementById("accountType");
var accountTypeSelected = accountType.options[accountType.selectedIndex].text;
var accountCurrency = document.getElementById("accountCurrency");
var accountCurrencySelected = accountCurrency.options[accountCurrency.selectedIndex].text;
var temporaryObject = {
'accountName': accountName,
'accountBalance': accountBalance,
'accountTypeSelected': accountTypeSelected,
'accountCurrencySelected': accountCurrencySelected
};
accountsArray.push(temporaryObject);
console.log(accountsArray);
saveAccountData();
showAccountsArray();
}
function saveAccountData() {
localStorage.setItem('accountsArray', JSON.stringify(accountsArray));
}
function showAccountsArray() {
//var accountsLocalStorage = JSON.parse(localStorage['accountsArray']);
if (localStorage.getItem("accountsArray") === null) {
document.getElementById("getStarted").style.visibility="visible";
document.getElementById("balanceToolbarName").style.visibility="hidden";
document.getElementById("accountsMainList").style.visibility="hidden";
} else {
var accountsLocalStorage = JSON.parse(localStorage['accountsArray']);
console.log(accountsLocalStorage);
var accountInfo = '';
var i = 0;
while (i < accountsLocalStorage.length) {
accountInfo += '<li class="swipeout"><div class="swipeout-content item-content"><div class="item-inner"><div class="item-title">' + accountsLocalStorage[i].accountName + '</div><div class="item-after">$' + accountsLocalStorage[i].accountBalance + '</div></div></div><div class="swipeout-actions-left"><a href="#" class="action1">Clear</div><div class="swipeout-actions-right">Delete</div></a></li>';
document.getElementById("accountsList").innerHTML = accountInfo;
i++;
}
document.getElementById("getStarted").style.visibility="hidden";
document.getElementById("balanceToolbarName").style.visibility="visible";
document.getElementById("accountsMainList").style.visibility="visible";
}
}
*
all of your functions work correctly as tested by the link you've provided. When the page loads it successfully retrieves the data (if any) from the local storage and displays on the page. However, the global array variable accountsArray is populated with data retrieved from the local storage.
You need to repopulate the global array otherwise when you call saveAccountData it will save whatever the array holds which indeed overrides whatever you had in the local storage. To fix it, simply add add this code block...
$(function(){
var data = localStorage.getItem("accountsArray");
if(data != null)
accountsArray = JSON.parse(localStorage.getItem("accountsArray"));
});

Categories