I have a JSON object employees which I would like to populate with the data in my localstorage. I first saved my JSON object to local storage using stringify() .
sessionStorage.setItem('Employee3', JSON.stringify({id: 3, firstName: 'Dwight', lastName: 'Schrute', title: 'Assistant Regional Manager', managerId: 2, managerName: 'Michael Scott', city: 'Scranton, PA', officePhone: '570-444-4444', cellPhone: '570-333-3333', email: 'dwight#dundermifflin.com', reportCount: 0}));
Now I want to populate my employees object:
employees: {},
populate: function() {
var i = i;
Object.keys(sessionStorage).forEach(function(key){
if (/^Employee/.test(key)) {
this.employees[i] = $.parseJSON(sessionStorage.getItem(key));
i++;
}
});
},
The function $.parseJSON(sessionStorage.getItem(key)) returns the JSON object correctly. Assigning it to the employees object fails:
Uncaught TypeError: Cannot set property 'undefined' of undefined
Array.forEach doesn't preserve this, so you'll have to preserve it yourself. Either of these will work (see mdn):
Object.keys(sessionStorage).forEach(function(key){
if (/^Employee/.test(key)) {
this.employees[i] = $.parseJSON(sessionStorage.getItem(key));
i++;
}
}, this);
var self = this;
Object.keys(sessionStorage).forEach(function(key){
if (/^Employee/.test(key)) {
self.employees[i] = $.parseJSON(sessionStorage.getItem(key));
i++;
}
});
Also, consider using the browser's JSON.parse() instead of jQuery's. Any browser that supports Array.forEach will support JSON.parse().
Yet another way:
Object.keys(sessionStorage).forEach((function(key){
if (/^Employee/.test(key)) {
this.employees[i] = $.parseJSON(sessionStorage.getItem(key));
i++;
}
}).bind(this));
Calling .bind(this) on a function will return a new function bound to the value for this (in the current scope).
the advantage to this is that you don't need to remember which methods support the second "value for this" parameter. It always works. For example, this also works for when adding event listeners to DOM nodes.
tjameson's first suggestion is probably to be preferred in this specific case.
You have a problem with the scope of this. When you are inside the foreach-callback this is not referring to the correct instance.
You need to save a reference to this before and then access the object through that reference (self in the following example):
function something(){
var self = this;
** snip **
employees: {},
populate: function() {
var i = i;
Object.keys(sessionStorage).forEach(function(key){
if (/^Employee/.test(key)) {
self.employees[i] = $.parseJSON(sessionStorage.getItem(key));
i++;
}
});
},
** snip **
}
You have a problem with the value of this inside the forEach callback. Also, And you don't need jQuery to parse JSON.
You can do this instead:
employees: {},
populate: function() {
var that = this;
var i = i;
Object.keys(sessionStorage).forEach(function(key){
if (/^Employee/.test(key)) {
that.employees[i] = JSON.parse(sessionStorage.getItem(key));
i++;
}
});
},
There is no reason to parse JSON, two simple functions will do the job (hope that was the idea):
var employees = {};
function setEmployee( data ) {
var id = data.id;
sessionStorage.setItem('Employee' + id, JSON.stringify( data ));
};
function getEmployee( id ) {
employees[id] = sessionStorage.getItem('Employee' + id);
};
var oneEmployee = {
id: 3,
firstName: 'Dwight',
lastName: 'Schrute',
title: 'Assistant Regional Manager',
managerId: 2,
managerName: 'Michael Scott',
city: 'Scranton, PA',
officePhone: '570-444-4444',
cellPhone: '570-333-3333',
email: 'dwight#dundermifflin.com',
reportCount: 0
};
setEmployee( oneEmployee );
getEmployee( 3 );
Related
https://jsfiddle.net/adamchenwei/Lyg2jy61/7/
RESOLVED VERSION:
https://jsfiddle.net/adamchenwei/Lyg2jy61/10/
For some reason findKey for parent top level got undefined
My objective is to get the result = 'myName' with lodash;
var obj = {
myName: {
Adam: 'Man',
},
herName: {
Eve: 'Woman',
},
};
var result = _.findKey(obj, '0'); //as you can see somehow its undefined!
var result2 = _.findKey(obj.myName, '0');
console.log(result);//objective is to get the result = 'myName' with lodash;
console.log(result2);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.16.4/lodash.min.js"></script>
It looks like what you're actually trying to do is get the first key of an object. To get the keys of an object, you can use _.keys. Then just retrieve the first one.
var obj = {
myName: {
Adam: 'Man',
},
herName: {
Eve: 'Woman',
},
};
console.log(_.keys(obj)[0]);
// Or if you want to do it exclusively with lodash functions
console.log(_.first(_.keys(obj)));
// or
console.log(_.head(_.keys(obj)));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.16.4/lodash.min.js"></script>
It's not clear what you're after exactly, but using _.findKey on the object you have posted would work like this:
https://jsfiddle.net/Lyg2jy61/8/
var obj = {
myName: {
Adam: 'Man',
},
herName: {
Eve: 'Woman',
}
};
console.log(_.findKey(obj, function(o) { return !!o.Adam; }));
If you just want the first key: _.keys(obj)[0]
I have an object that I am creating and a function on that object to load data into the various properties. While the method works as desired, I feel like it might be redundant. Can this be accomplished in a more concise or better way?
var user = {
productLine: {
userActiveValue: []
},
id: {
PACT: null,
EDIPI: null,
AKO: null,
},
name: {
first: null,
last: null,
},
DMIS: null,
region: null,
email: null,
load: true,
loadUser: function (userInfoAPIResponse) {
this.id.PACT = userInfoAPIResponse.UID;
this.id.EDIPI = userInfoAPIResponse.EDIPN;
this.id.AKO = userInfoAPIResponse.akoUserID;
this.name.first = userInfoAPIResponse.fName;
this.name.last = userInfoAPIResponse.lName;
this.DMIS = userInfoAPIResponse.dmisID;
this.region = userInfoAPIResponse.RHCName;
this.email = userInfoAPIResponse.userEmail;
console.log(this);
}
};
function User(userInfoAPIResponse) {
this.id = {
PACT: userInfoAPIResponse.UID,
EDIPI: userInfoAPIResponse.EDIPN,
AKO: userInfoAPIResponse.akoUserID
};
this.productLine = {
userActiveValue: []
};
this.name = {
first: userInfoAPIResponse.fName,
last: userInfoAPIResponse.lName
};
this.DMIS = userInfoAPIResponse.dmisID;
this.region = userInfoAPIResponse.RHCName;
this.email = userInfoAPIResponse.userEmail;
}
var user = new User(...);
Aside from using e.g. user.name = {first: response.fName, last: response.lName} and so on, no. You need to map the variables from one object to another yourself, or just use the response as your user variable.
Alternatively you could just declare user as global (or outer) scope and both declare and set the sub-objects in your callback function. This would mean you potentially had to check for them and their parents being undefined before using them elsewhere, as opposed to a simple not null check.
I've got a flat JavaScript object like this:
{
id: 3726492,
kind: 'user',
permalink: 'nicholas',
username: 'Nicholas',
...
a lot more attributes
}
I'd like to create a new object which only has a subset of the attributes of the original object.
Something like
var newObject = oldObject.fields(['id', 'username']);
newObject would be
{
id: 3726492,
username: 'Nicholas'
}
Is there already something like this?
Try this
function pick(data, keys) {
var result = {};
keys.forEach(function (key) {
if (data.hasOwnProperty(key)) {
result[key] = data[key];
}
});
return result;
}
var data = {
id: 3726492,
kind: 'user',
permalink: 'nicholas',
username: 'Nicholas'
}
var newData = pick(data, ['id', 'kind']);
console.log(newData);
In underscorejs or lodash there is method .pick
var data = {
id: 3726492,
kind: 'user',
permalink: 'nicholas',
username: 'Nicholas',
};
var newObject = _.pick(data, 'id', 'username');
console.log(newObject);
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore.js"></script>
You can use Array.prototype.reduce to reduce one object to another using the list of properties:
function subset(obj, propList) {
return propList.reduce(function(newObj, prop) {
obj.hasOwnProperty(prop) && (newObj[prop] = obj[prop]);
return newObj;
}, {});
}
var obj = {
id: 3726492,
kind: 'user',
permalink: 'nicholas',
username: 'Nicholas'
};
var newObj = subset(obj, ['id', 'username']);
console.log(newObj);
document.getElementById('json').innerText = JSON.stringify(newObj);
<pre id="json"></pre>
Not built-in, but you can sure define a simple function that does the job:
var original = {a:1112, b:434, c:666, d:222};
function fieldSubset(obj, fields) {
var subsetClone = {};
for( var i=0,l=fields.length; i<l; i++) {
// This can prevent filling undefined as properties
if(obj.hasOwnProperty(fields[i])) {
subsetClone[fields[i]] = obj[fields[i]];
}
}
return subsetClone;
}
fieldSubset(original, ["a", "c"]);
You can also use this in Object.prototype, but be aware that this might happen to conflict with native API in the future versions of JavaScript:
var original = {a:1112, b:434, c:666, d:222};
Object.defineProperty(Object.prototype, "fieldSubset", {
value: function(fields) {
var subsetClone = {};
for( var i=0,l=fields.length; i<l; i++) {
// This can prevent filling undefined as properties
if(this.hasOwnProperty(fields[i])) {
subsetClone[fields[i]] = this[fields[i]];
}
}
return subsetClone;
},
enumerable: false,
configurable: true}
);
original.fieldSubset(["a", "c"]);
One liner using Array.prototype.reduce. We are also using Object.assign. The idea is to keep extending a blank object with the keys found in the filters array. If you see, the reduce function takes a callback function with arg1,arg2,arg3 params as the first argument and an empty object as the second argument. This object will be cloned and extended with the help of the keys specified in the filters array.
var a = {
id: 3726492,
kind: 'user',
permalink: 'nicholas',
username: 'Nicholas',
};
var filters = ["id","username","permalink"];
var sub = Object.keys(a).reduce((arg1,arg2,arg3)=>{ var res = {}; if(filters.indexOf(arg2)>=0){ res[arg2] = a[arg2]; } return Object.assign(arg1,res);},{})
console.log(sub);
You haven't specifically mentioned what is the type of values behind your object's keys. Your current answers cover the shallow copy and deep copy.
Another alternative would be to create a view of the original object. This would be helpful if you have very large data objects and you do not want them copy in the memory.
function View(obj, properties) {
var view = {};
properties.forEach(function(prop) {
Object.defineProperty(view, prop, {
get: function() {
return obj[prop];
},
set: function(val) {
obj[prop] = val;
},
enumerable: true,
configurable: true
});
});
return view;
}
then with your data you can do:
var data = {
id: 3726492,
kind: 'user',
permalink: 'nicholas',
username: 'Nicholas',
},
view = new View(data, ['id', 'username']);
view.id; // 3736492
view.username; // Nicholas
of course you have to be aware that you can change your original object just by view.id = 'something else'. However it is easily preventable.
I'm parsing data returned from Eventbrite's API, and forming objects from the name of the event and any img tags scraped from the entirety of data response.
for (var i = 1, l = rawEventbriteData.length; i < l; i++){
var eventObject = {
name: rawEventbriteData[i].event.title,
images: []
};
var jsdom = require('jsdom');
var arrayOfImgs = [];
jsdom.env({
html: rawEventbriteData[i].event.description,
scripts: ["http://code.jquery.com/jquery.js"],
done: function(errors, window) {
window.$('img').each(function(){
var imgSrc = window.$(this).attr('src');
console.log(imgSrc);
eventObject.images.push(imgSrc);
});
}
});
}
I log out the result:
the events are [ { name: 'Sense 5K - Austin Pre-Registration', images: [] },
{ name: 'Tech Snack - Fostering Student Research', images: [] },
{ name: 'Coffee,Dessert and Chat', images: [] },
{ name: 'Texas Table Tennis!', images: [] },
{ name: '#3and3 w/ #FortyCreek_John', images: [] },
{ name: 'BUSINESS COACHING | CONVERSE-OVER-COFFEE (Austin)',
images: [] },
{ name: 'Tiny House in Austin, Texas', images: [] },
{ name: 'Fastest House Tour in America', images: [] },
{ name: 'Texas NORML\'s Monthly Open Meeting, Dreadneck Wednesday and Smokin\' Stand-Up',
images: [] },
{ name: 'Amazing Scavenger Hunt Adventure-Austin', images: [] } ]
https://evbdn.eventbrite.com/s3-s3/eventlogos/90039995/about.png
https://evbdn.eventbrite.com/s3-s3/eventlogos/90039995/bluedawntour1.jpg
And we can see that the each event object has the name property as expected, but the array of images remains empty. The image sources do end up logging at the end of the terminal output (called within the jsdom function), but clearly these urls are not available when they are needed.
I want to end up with eventObjects that have the name as they currently do, as well as the array of images that currently doesn't come back in time. What is a good approach for solving this?
This is the classic closure problem. The function you're passing as the done handler has an enduring reference to the eventObject variable (and there's only one, it's not loop-specific), so only the last one gets filled in. (This is usually what you want, but not in this case.)
You can work around it with a builder function that closes over something else (usually an argument):
for (var i = 1, l = rawEventbriteData.length; i < l; i++){
var eventObject = {
name: rawEventbriteData[i].event.title,
images: []
};
var jsdom = require('jsdom');
var arrayOfImgs = [];
jsdom.env({
html: rawEventbriteData[i].event.description,
scripts: ["http://code.jquery.com/jquery.js"],
done: buildDoneHandler(eventObject)
});
}
function buildDoneHandler(evtobj) {
return function(errors, window) {
window.$('img').each(function(){
var imgSrc = window.$(this).attr('src');
console.log(imgSrc);
evtobj.images.push(imgSrc);
});
};
}
Note that the functon returned by buildDoneHandler closes over and uses evtobj (the argument we give buildDoneHanlder) rather than eventObject. Since the argument is specific to each call to buildDoneHandler, we update the right event object.
This can also be done with ES5's Function#bind although it can get confusing when there are other arguments, and creating functions that you bind in a loop is wasteful (not that it usually matters):
for (var i = 1, l = rawEventbriteData.length; i < l; i++){
var eventObject = {
name: rawEventbriteData[i].event.title,
images: []
};
var jsdom = require('jsdom');
var arrayOfImgs = [];
jsdom.env({
html: rawEventbriteData[i].event.description,
scripts: ["http://code.jquery.com/jquery.js"],
done: function(evtobj, errors, window) {
window.$('img').each(function(){
var imgSrc = window.$(this).attr('src');
console.log(imgSrc);
evtobj.images.push(imgSrc);
});
}.bind(null, eventObject)
});
}
Function#bind returns a function that, when called, will call the original function with a specific this value and with any further arguments you give it at the beginning of the arguments list.
I want to implement some sort of hasObject function with underscore.js.
Example:
var Collection = {
this.items: [];
this.hasItem: function(item) {
return _.find(this.items, function(existingItem) { //returns undefined
return item % item.name == existingItem.name;
});
}
};
Collection.items.push({ name: "dev.pus", account: "stackoverflow" });
Collection.items.push({ name: "margarett", account: "facebook" });
Collection.items.push({ name: "george", account: "google" });
Collection.hasItem({ name: "dev.pus", account: "stackoverflow" }); // I know that the name would already be enough...
For some reason underscores find returns undefined...
What am I doing wrong?
It looks like you are reading underscore documentation too literally, where
they have:
var even = _.find([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });
However, this doesn't make any sense for your case, you just want to see if the .name property is equal to
some other object's .name, like this:
var Collection = {
items: [],
hasItem: function(item) {
return _.find(this.items, function(existingItem) { //returns undefined
return item.name === existingItem.name;
});
}
};
You need to check both values for the name and the account.
var Collection = {
this.items: [];
this.hasItem: function(target) {
return _.find(this.items, function(item) {
return item.name === target.name && item.acount === target.account;
});
}
};
Have you considered using Backbone.js? It fulfills all your collection management needs and uses underscore's methods too.
// create a collection
var accounts = new Backbone.Collection();
// add models
accounts.add({name: 'dev.pus', account: 'stackoverflow'});
accounts.add({name: 'margarett', account: 'facebook'});
accounts.add({name: 'george', account: 'google'});
// getting an array.
var results = accounts.where({ name: 'dev.pus', account: 'stackoverflow' });