Is there a simpler/cleaner way to write this? - javascript

I am using this code to sync with google storage. It works just want to know if there is a simpler and cleaner way to write this.
chrome.storage.sync.get(['titleCustom', 'textCustom', 'highCustom', 'quoteCustom', 'bgCustom'], (result) => {
if(result.titleCustom !== undefined){
document.documentElement.style.setProperty('--blue', result.titleCustom);
}
if(result.textCustom !== undefined){
document.documentElement.style.setProperty('--light-text', result.textCustom);
}
if(result.highCustom !== undefined){
document.documentElement.style.setProperty('--green', result.highCustom);
}
if(result.quoteCustom !== undefined){
document.documentElement.style.setProperty('--dark-text', result.quoteCustom);
}
if(result.bgCustom !== undefined){
document.documentElement.style.setProperty('--light-bg', result.bgCustom);
}
}

You could create a object mapping the key in result (e.g. titleCustom) to the respective color (e.g. --blue), then iterate through the object and so something like this:
// key:color are the variables of our outer for-each loop
if(result[key] !== undefined) {
document.documentElement.style.setProperty(color, result[key]);
}
This has the additional effect that you can use Object.keys(mapObject) as parameter in chrome.storage.sync.get instead of the hard-coded array.

Declare an object of key/values for the storage items where the key is the storage item and the value is the corresponding property you'd like to set for that key element.
let props = {
"titleCustom": "--blue",
"textCustom": "--light-text",
"highCustom" :"--green",
"quoteCustom": "--dark-text",
"bgCustom": "--light-bg"
}
];
let keys = Object.keys(props);
chrome.storage.sync.get(keys, (result) => {
keys.forEach((element) => {
if (element in result) {
document.documentElement.style.setProperty(props[element], result[element]);
}
});
});

Related

Getting objects from json data tree, issue with reusing function

I wrote a code (with help from another question) in order to get objects from a json tree.
let objFound = null;
function getObjFromValue(obj, prop, val) {
Object.keys(obj).forEach((key) => {
if (objFound == null) {
if (key === prop && val === obj[key]) {
objFound = obj;
}
if (typeof obj[key] === 'object') {
getObjFromValue(obj[key], prop, val);
}
}
});
return objFound;
}
The problem with it is, that I can use it only once since the variable objFound is global (I need it to stop the forEach loop somehow) and never gets nulled again, so when I make another search I receive the previous and first object that the function found. Is there a way to improve/fix my function?
You can use a local variable to return the value, after setting the global variable to null.
let toReturn = objFound;
objFound = null;
return toReturn;
However, this global variable approach might not be the best one. The comment of #pilchard has a good alternative, making the variable local.

Add a default value for a json property when it doesn't exist in Javascript

So I'm trying to edit this rss feed with these 2 functions because of the media:content property which I have had no luck accessing directly. the functions I have below work for creating a new value called mediaContent which I can then easily access. The issue is in the rss feed not all objects will have media:content and I want to add a default value for the objects that don't have that property so I have consistency in my objects. Otherwise I end up with undefined on on some of mediaContent in my new object. I wanted to start just added a default value in when media:content is not present in the object but these ||'s are not working as I would have expected. How can I get my else if to punch in a default value if media:content does not exist? I'm probably missing something easy.
function getMediaContent(value) {
for (var i in value) {
if (i === "media:content") {
console.log("MC::", i)
return value[i].$;
} else if (i !== "title" || i !== "link" || i !== "pubDate" || i !== "isoDate" || i !== "guid" || i !== "contentSnippet" || i !== "content") {
debugger;
return "no media content"
}
}
}
function getNewsLinks() {
return newsItems.map(value => ({
value,
mediaContent: getMediaContent(value)
}))
}
SOLUTION (based on accepted answer)
function getMediaContent(value) {
return "media:content" in value ? value["media:content"].$ : "no media content";
}
works perfectly. Thanks!
Since you're just looking to see if a property exists on an object, you can use the in operator:
function getMediaContent(value) {
return "media:content" in value ? value["media:content"].$ : "no media content";
}
That checks if the property exists, and if so, gets the value of its $ property. Otherwise, returns the default value.
I needed something similar and optionally it would work for multi-layered JSON objects. Here is the function I use:
function getFromJSON(obj, ...args) {
for (const arg of args) {
if (!Array.isArray(arg)) {
if (arg in obj) {
obj = obj[arg]
} else {
return `${arg} not found in JSON`;
}
} else {
for (const argOpt of arg) {
if (argOpt in obj) {
obj = obj[argOpt]
break;
}
}
}
}
return obj
}
In addition, you can pass multiple keys in an array if you want to get the value of whichever exists.

filter() in function is affecting another

In the below, I have a function that should be filtering accountView, but for some reason it's also filtering accountCompare. Not sure why this is happening. I thought I had assigned the two seperately so that accountCompare is always a constant.
getAccount() {
this.accounts.getAccount(this.accountId).subscribe(
response => {
this.accountView = this.apiHandler.responseHandler(response);
this.accountCompare = this.apiHandler.responseHandler(response);
console.log(this.accountCompare);
},
(err) => {
this.apiHandler.errorHandler(err);
}
);
}
//then in this function, I filter accountView, however it appears to also be affecting accountCompare as well.
userDelete(id) {
if (this.accountCompare.users.some(item => item.id === id)) {
this.accountForm.value.usersToDelete.push(id);
}
this.accountView.users = this.accountView.users.filter(user => user.id !== id);
/* this.accountForm.value.usersToAdd = this.accountForm.value.usersToAdd.filter(user => id !== id); */
console.log(this.accountCompare);
}
Non-primitive values are passed by reference. This means you are actually updating a reference, not a value.
A quick hack for you :
this.accountView = JSON.parse(JSON.stringify(this.apiHandler.responseHandler(response)));
this.accountCompare = JSON.parse(JSON.stringify(this.apiHandler.responseHandler(response)));

Array that can store only one type of object?

Is it possible to create an array that will only allow objects of a certain to be stored in it? Is there a method that adds an element to the array I can override?
Yes you can, just override the push array of the array (let's say all you want to store are numbers than do the following:
var myArr = [];
myArr.push = function(){
for(var arg of arguments) {
if(arg.constructor == Number) Array.prototype.push.call(this, arg);
}
}
Simply change Number to whatever constructor you want to match. Also I would probably add and else statement or something, to throw an error if that's what you want.
UPDATE:
Using Object.observe (currently only available in chrome):
var myArr = [];
Array.observe(myArr, function(changes) {
for(var change of changes) {
if(change.type == "update") {
if(myArr[change.name].constructor !== Number) myArr.splice(change.name, 1);
} else if(change.type == 'splice') {
if(change.addedCount > 0) {
if(myArr[change.index].constructor !== Number) myArr.splice(change.index, 1);
}
}
}
});
Now in ES6 there are proxies which you should be able to do the following:
var myArr = new Proxy([], {
set(obj, prop, value) {
if(value.constructor !== Number) {
obj.splice(prop, 1);
}
//I belive thats it, there's probably more to it, yet because I don't use firefox or IE Technical preview I can't really tell you.
}
});
Not directly. But you can hide the array in a closure and only provide your custom API to access it:
var myArray = (function() {
var array = [];
return {
set: function(index, value) {
/* Check if value is allowed */
array[index] = value;
},
get: function(index) {
return array[index];
}
};
})();
Use it like
myArray.set(123, 'abc');
myArray.get(123); // 'abc' (assuming it was allowed)

lodash where method to consider nested array elements [duplicate]

I have an object of folders/files that looks like this:
{
about.html : {
path : './about.html'
},
about2.html : {
path : './about2.html'
},
about3.html : {
path : './about3.html'
},
folderName : {
path : './folderName',
children : {
sub-child.html : {
path : 'folderName/sub-child.html'
}
}
}
}
And it can go 6-7 levels deep of folders having children.
I want to find the object where path is equal to a string that I provide. Regardless of how deep it is.
I'm using underscore which only does top level:
_.findWhere(files,{path:'./about2.html'}
How can I do a deep, nested search. Does underscore have something for this or do I need to build a mixin with recursion?
This isn't the prettiest code, but I tested it out and it seems to work the way you are asking. It's setup as a lodash/underscore mixin, but can be used however. Usage would be like this:
_.findDeep(testItem, { 'path': 'folderName/sub-child.html' })
Implementation:
findDeep: function(items, attrs) {
function match(value) {
for (var key in attrs) {
if(!_.isUndefined(value)) {
if (attrs[key] !== value[key]) {
return false;
}
}
}
return true;
}
function traverse(value) {
var result;
_.forEach(value, function (val) {
if (match(val)) {
result = val;
return false;
}
if (_.isObject(val) || _.isArray(val)) {
result = traverse(val);
}
if (result) {
return false;
}
});
return result;
}
return traverse(items);
}
Instead of findWhere, use filter, which takes a function as the predicate rather than a key-value map. Use a recursive function to check the current node and possible children. Something like this:
var searchText = './about2.html';
var recursiveFilter = function(x) {
return x.path == searchText ||
( typeof x.children != 'undefined' && recursiveFilter(x.children['sub-child.html']) );
};
_.filter(files, recursiveFilter);
Edit
Assuming this works, you'll probably want to make a function getRecursiveFilter(searchText). Here's how that would look:
function getRecursiveFilter(searchText) {
var recursiveFilter = function(x) {
return x.path == searchText ||
(typeof x.children != 'undefined'
&& arguments.callee(x.children['sub-child.html']) );
};
return recursiveFilter;
}
Note that here, recursiveFilter uses arguments.callee to call itself recursively.
Here's a working demo.
This already has an accepted answer, but this other answer was very clean and perfect for my similar situation: https://stackoverflow.com/a/21600748/1913975
_.filter +_.where
Though accepted answer works, it's too generic - it searches all the properties of an object to find children. I am proposing introducing an extra parameter, called 'recursProperty' which will be considered to go deep in the object. This solution is also setup to be used as lodash/underscore mixin and extends loadash/underscore capabilities.
_.findDeep = function(collection, predicate, recursProperty){
let items = [];
_.each(collection, each => items.push(each));
return _.find(items, function(value, key, coll){
if (predicate(value, key, coll)){
return true;
} else {
_.each(value[recursProperty], each => items.push(each));
}
});
};
It can be used as any other underscore function. e.g,
_.findDeep(self.baseEntities, baseEntity => baseEntity.id === 71, 'entity');
Not providing proper value for 'recursProperty' argument or providing null/undefined will simply make the search only on first level (no going deep).

Categories