Issue with Javascript Variable Scope - javascript

I have a variable mutedUser which I would like to have persist to another function. I am having a bit of trouble with the variable persisting outside the click event. What would be the best way to have it so the "return mutedUser" would keep the "muted" string addition based on the conditions of the if statement being met? Thanks!
*The console.log's were me checking to see where the persistance stops
this.isUserMuted = function isUserMuted(payload) {
var mutedUser = '';
// If mute button is clicked place them into muted users list
// check for duplicates in list
$("#messages-wrapper").off('click', '.message button.muteButton');
$("#messages-wrapper").on('click', '.message button.muteButton', function(e) {
$('#unMute').show();
//create userId reference variable
var chatUserID = parseInt($(this).parent().parent().attr("data-type"));
//store userId in muted user object
mutedUsers[chatUserID] = {};
mutedUsers[chatUserID].id = chatUserID;
mutedUsers[chatUserID].muted = true;
if (mutedUsers[chatUserID] !== null && mutedUsers[chatUserID].id === payload.a) {
console.log("user is now muted");
mutedUser += ' muted';
console.log(mutedUser + 1);
}
console.log(mutedUser + 2);
});
return mutedUser;
};

If I understood what you're trying to do (by looking at the code), this would be the best approach:
// If mute button is clicked place them into muted users list
// check for duplicates in list
$("#messages-wrapper").off('click', '.message button.muteButton');
$("#messages-wrapper").on('click', '.message button.muteButton', function(e) {
$('#unMute').show();
//create userId reference variable
var chatUserID = parseInt($(this).parent().parent().attr("data-type"));
//store userId in muted user object
mutedUsers[chatUserID] = {};
mutedUsers[chatUserID].id = chatUserID;
mutedUsers[chatUserID].muted = true;
});
this.isUserMuted = function isUserMuted(payload) {
var mutedUser = '';
if (mutedUsers[payload.a] !== null) {
mutedUser += ' muted';
}
return mutedUser;
};
The code retains the array of mutedUsers, and isUserMuted function checks if provided user is in that array. In the code you provided, you would attach a new event handler every time isUserMuted function is called..
The isUserMuted function could even be shortened to:
this.isUserMuted = function isUserMuted(payload) {
return mutedUsers[payload.a] !== null ? ' muted' : '';
};

Edit
Sorry, my mistake. Another way is to pass in that variable, i.e.
this.isUserMuted = function isUserMuted(payload, isMuted) {
isMuted = '';
// If mute button is clicked place them into muted users list
// check for duplicates in list
$("#messages-wrapper").off('click', '.message button.muteButton');
$("#messages-wrapper").on('click', '.message button.muteButton', function(e) {
$('#unMute').show();
//create userId reference variable
var chatUserID = parseInt($(this).parent().parent().attr("data-type"));
//store userId in muted user object
mutedUsers[chatUserID] = {};
mutedUsers[chatUserID].id = chatUserID;
mutedUsers[chatUserID].muted = true;
if (mutedUsers[chatUserID] !== null && mutedUsers[chatUserID].id === payload.a) {
console.log("user is now muted");
isMuted += ' muted';
console.log(mutedUser + 1);
}
console.log(mutedUser + 2);
});
return isMuted;
};

You can't. If you return a string from the function, it will always be passed by value, i.e. copied; and it's value will not change any more. You would need to return a function that can access the current value of the local variable, or an object with a property that is changing.
As you already seem to have an object, option#2 will fit in well here:
function User() { // or whatever you have
…
var user = this;
// If mute button is clicked place them into muted users list
// check for duplicates in list
$("#messages-wrapper").on('click', '.message button.muteButton', function(e) {
$('#unMute').show();
//store userId in muted user object
mutedUsers[user.id] = user;
user.muted = true;
});
this.muted = false;
this.isUserMuted = function() {
return this.muted ? ' muted' : '';
}
}

Related

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".

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

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

loadItem(); with localStorage (broken)

I have two function to saveItem() and other to loadItem(); but I need see my items "when I Refresh the Page", I am using localStorage to save data doing of this a JSON.
var input = document.getElementById('input');
function newItem(list, itemText){
var item = document.createElement('li');
item.className = 'item';
item.innerText = itemText;
list.appendChild(item);
saveItem();
}
input.onkeyup = function(evt){
var key = evt.keyCode || evt.whitch;
if(key == 13){
itemText = input.value;
console.log('createITem');
if(!itemText || itemText == '' || itemText == ' '){
return false;
}
newItem(document.getElementById('ul'), itemText);
}
}
function saveItem(){
var items = document.querySelector('li.item');
var data = Array.prototype.map.call(items, function(item){
return [item.innerHTML];
});
localStorage.setItem('data', JSON.stringify(data));
}
function loadItem(){
var items = JSON.parse(localStorage.getItem('data'));
if(!items){
return;
}
Array.prototype.map.call(items, function(item){
return newItem(document.getElementById('content-memo'), item[0]);
});
}
loadItem();
Are you sure saveItem is working? Your code shows you calling loadItem, but it doesn't show you calling saveItem. In any case, that's where your problem is.
If you open your Dev Tools pane and inspect localStorage (or from the console, see if localStorage.data is defined), you should see if it's working properly. If not, then of course loadItem won't work as expected.
In order to map all li.items, you have to change the line from:
var items = document.querySelector('li.item');
To this:
var items = document.querySelectorAll('li.item');
querySelector will only return the first result as a DOM object, and you can't call Array.prototype.map on it. You need an array-like object. queryItemSelectorAll gives you that.
As it stands, the Array.prototype.map call in saveItem returns an empty array. So that's what gets set in localStorage.data - and thus what gets returned to the map function in loadItem.
Aside from that, are you having other troubles?

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

On first time visit- popup a div asking user name, then store it and the date on local storage

the problem here is it never goes into the else statement; I already tried creating a flag to check when it goes into the if and changing but it didn't work
var oUser = {};
// This is to add Name to JS
if (!oUser.name) {
oUser.name = prompt("Enter Name: ") // This is to add Name to JS
localStorage.name = oUser.name
// Now the time
oUser.date = new Date().toUTCString();
localStorage.date = oUser.date;
} else {
var msgDis = document.getElementById('msgDisplay');
msgDis.innerHTML = "Hi " + localStorage.name + " Welcome back!" + " -->Date: " + localStorage.date;
}
oUser.name is undefined, so !oUser.name will always pass. You're creating oUser as an empty Object (var oUser = {};), then checking a data member you never defined.
You should be checking if the localStorage is set:
// Declare oUser in the global scope, as an empty object
var oUser = {};
// check for browser support of localStorage
if(typeof(localStorage) == 'undefined') {
// Check failed, alert user
alert('Your browser does not support the localStorage method!');
} else {
// wrapping this in a try...catch block, incase cookies are disabled
try {
// Attempt to pull oUser (by key) from localStorage, failure results
// in oUser being an empty object.
oUser = JSON.parse(localStorage.getItem('oUser'))||{};
// Now check if oUser.name is NOT set
if(!oUser.name) {
// prompt user for a name
oUser.name = prompt("Enter Name: ");
// insert current date
oUser.date = (new Date()).toUTCString();
// save oUser in localStorage, stringified
localStorage.setItem('oUser',JSON.stringify(oUser));
} else {
// oUser.name was set, welcome them back
var msgDis = document.getElementById("msgDisplay");
msgDisplay.innerHTML = "Hi " + oUser.name + " Welcome back! -->Date: " + oUser.date;
}
} catch(e) {
// Cookies are disabled, which threw an error, alert the user
alert('To use localStorage, you need to enable cookies.');
}
}
Just a neat example I came up with:
LocalStorage the fun way:
LIVE DEMO
LocalStorage Script:
var LS = {
get : function(id) {
var item=localStorage.getItem(id);
return item?(item.charAt(0)=='{'?JSON.parse(item):item):{};
},
set : function(id, key) {
var k=typeof key=="object"?JSON.stringify(key):key;
return localStorage.setItem(id,k);
},
del : function(id){
return localStorage.removeItem(id);
}
};
Basic usage:
LS.set('SomeItemID', "String"||Object ); // Store a String or even an Object!
LS.get('SomeItemID'); // Get your String or your Object;
LS.del('SomeItemID'); // Deletes that LocalStorage item
In your case you can use this script like:
var oU = LS.get('oUser');
// if oUser exists as a LocalStorage key name, "oU" should now look like:
// oU = {name:"somename", date:"somedate"};
if(!oU.name){
oU.name = prompt("Enter Name: ");
oU.date = new Date().toUTCString();
LS.set('oUser', oU); // Store back the whole object
}else{
var msgDis = document.getElementById('msgDisplay');
msgDis.innerHTML = "Hi " + oU.name + " Welcome back!" + " Date: " + oU.date;
}
if you want to delete that item from your localStorage than just do:
LS.del('oUser');
If you want to add more features / fixes,
here's the script reverse-engeenered:
var LS = {
get : function(id) {
var item=localStorage.getItem(id); // Read LocalStorage(id)
if(item){
if(item.charAt(0)=='{'){ // If is String has "{" (is an JSON)
return JSON.parse(item); // Parse to get the Object
}else{
return item; // else return that string
}
}else{
return {}; // return an empty object
}
},
set : function(id, key) {
var k;
if(typeof key=="object"){ // If we're about to store an Object
k = JSON.stringify(key); // transform it to String
}else{
k = key; // else store whatever is key
}
return localStorage.setItem(id,k); // Send to LocalStorage
},
del : function(id){
return localStorage.removeItem(id);// Delete LocalStorage(id)
}
};

Categories