Example in the documentation:
App.Person = Ember.Object.extend({
// these will be supplied by `create`
firstName: null,
lastName: null,
fullName: function() {
return this.get('firstName') + ' ' + this.get('lastName');
}.property('firstName', 'lastName')
});
var ironMan = App.Person.create({
firstName: "Tony",
lastName: "Stark"
});
ironMan.get('fullName') // "Tony Stark"
I can't understand why I must specify dependencies like .property('firstName', 'lastName') if I had indicated what needs to return: return this.get('firstName') + ' ' + this.get('lastName') and the example above can work without it (just using property()).
Thanks.
It's for caching.
From docs:
By default the function backing the computed property will only be
called once and the result will be cached. You can specify various
properties that your computed property is dependent on. This will
force the cached result to be recomputed if the dependencies are
modified.
Related
This question already has answers here:
Push is overwriting previous data in array
(2 answers)
Closed 3 months ago.
I'm new to node and javascript and have been banging my head on the following. I've created an object as follows:
var Subscriber = {
'userID': String,
'email': String,
'name': String,
'stage': String,
'poster': Boolean,
'canEmail': Boolean,
'stage': String, }
I have a function where I query mongodb, and loop through the results, attempting to load an array of subscribers, which I've declared as:
var s = Subscriber;
var subscribers = [];
The loop looks like this:
//load array of users that are subscribed to the group
async.forEach(g.subscribers, function(item, callback) {
//load user document for this user
User.findOne({ _id: item}, function(err, u) {
if(!err && u) {
//var s = new Subscriber();
console.log('Sub load, found user %s, building array item', u.email);
console.log('Subs # loop start');
console.log(util.inspect(subscribers));
console.log('Heres foo: ' + util.inspect(foo));
s.userID = u._id;
s.email = u.email;
s.name = u.firstName + ' ' + u.lastName;
s.stage = u.stage;
s.poster = false; //we're just loading subscribers at this point'
if(s.stage != 'new') s.canEmail = true;
//push new subscriber onto the array
console.log('Pushing ' + util.inspect(s));
subscribers.push(s);
console.log('At end ' + util.inspect(subscribers));
foo.push(s.email);
console.log('Heres foo now: ' + util.inspect(foo));
callback(null, item);
}
After each call to subscribers.push(s), the array has the correct number of elements, but all elements match the last values for s, like this (with two different users being pulled from the DB):
[ { userID: 4fc53a71163006ed0f000002,
email: 'test#test.com',
name: 'undefined undefined',
stage: 'new',
poster: false,
canEmail: true },
{ userID: 4fc53a71163006ed0f000002,
email: 'test#test.com',
name: 'undefined undefined',
stage: 'new',
poster: false,
canEmail: true } ]
Pushing a single element of s rather than the whole object seems to be fine. I added the "foo" array as a test, and it works fine:
Heres foo now: [ 'email1#foo.com', 'test#test.com' ]
What is going on here?!?!??!
The problem is not with the push method of the Array.prototype but with your bindings.
You are modifying the same s object in every iteration in your async.foreach block which is actually the same Object as the previously defined Subscriber.
First you should move the declaration of the s variable to the foreach block.
And also if you want to create an object with default values, it should be a function, which returns a new object:
function Subscriber() {
return {
'userID': '',
'email': '',
'name': '',
'stage': '',
'poster': false,
'canEmail': false,
'stage': ''
};
};
And then you can instantiate a Subscriber object like this:
var s = Subscriber();
See this answer or Closures on MDN for more explanation.
Cloning the object before pushing into the array, also solves the problem.
temp = clone(s);
subscribers.push(temp);
Get https://www.npmjs.com/package/clone
I have an array of objects that needs sorting by last name, however the object only has firstname and fullname parameters, so for last name I have to use fullname.replace((firstname + " "), ""). Example array below.
const names = [
{
firstname: "John",
fullname: "John Doe"
},
{
firstname: "Amber",
fullname: "Amber Smith"
},
{
firstname: "Michael",
fullname: "Michael Smith"
},
{
firstname: "Jessica",
fullname: "Jessica Kelly Charles"
}
]
While I can use the "replace" every time inside a sort compare function, I'd much rather have something like this:
names.sort(function(a, b) {
const lastname = fullname.replace((firstname+ " "), "");
if (a.lastname < b.lastname) {
return -1;
} else if (a.lastname > b.lastname) {
return 1;
}
return 0;
});
Obviously lastname comes up as undefined.
This has been rather hard to google for, and I think I'm missing some JavaScript basics here, but would greatly apprecite your help in helping me learn to write better code.
Your best bet is to modify the source of the array so it stores lastname upon collection.
If you can't do that:
Unless you do a prep pass through your array adding a lastname property, you'll have to compute it each and every time your sort callback is called, for both a and b.
names.sort((a, b) => {
const alast = a.fullname.replace(a.firstname + " "), "");
const blast = b.fullname.replace(b.firstname + " "), "");
return alast.localeCompare(blast);
});
(Note I used localeCompare, which is always a better choice for names and other natural language strings than < and >. For instance, ask the French whether รง should really come after z as it does with < and >. ;-) )
That will recompute the lastname for the same object repeatedly, though, since the same object may be passed to sort (as either a or b) repeatedly. If you think that may be a problem, you could do that prep pass I mentioned:
// Build a map of entry to last names
const lastnames = new Map(names.map(entry => {
const lastname = entry.fullname.replace(entry.firstname + " ", "");
return [entry, lastname];
}));
// sort
names.sort((a, b) => {
return lastnames.get(a).localeCompare(lastnames.get(b));
});
You are just declaring a new variable trying to access it via object's property. You should map your data in the first place like:
const names = [
{
firstname: "John",
fullname: "John Doe"
},
{
firstname: "Amber",
fullname: "Amber Smith"
},
{
firstname: "Michael",
fullname: "Michael Smith"
},
{
firstname: "Jessica",
fullname: "Jessica Kelly Charles"
}
].map(person => ({
...person,
lastName: person.fullName.replace(person.firstName + ' ', '')
}));
After mapping your data like that, you can use .lastName in the place you need.
names.sort(function(a, b) {
if (a.lastName < b.lastName) {
return -1;
} else if (a.lastName > b.lastName) {
return 1;
}
return 0;
});
I made a mistake with the age property on these objects and when I try to reassign different values later, it won't change it at all.
I want this to work with the Object.create Method. What should I do to fix this?
var personProto = {
calculateAge: function() {
console.log(2019 - this.yearOfBirth)
},
fullName: function() {
console.log(this.name + ' ' + this.lastName)
}
}
var sam = Object.create(personProto, {
name: { value: "samuel" },
yearOfBirth: { value: 1092 },
lastName: { value: "max" },
job: { value: "developer" }
});
sam.yearOfBirth = 1992;
sam.calculateAge(); // 927
console.log(sam.calculateAge()); is giving me 927 assuming yearsOfBirth is still 1092 even if I changed it to 1992 and the output was supposed to be 27.
By default, properties assigned using Object.create are not writable. Attempting to write to a non-writable property is a silent error in JavaScript - one of many reasons to use Strict Mode.
Here is your code again, but in Strict Mode:
'use strict';
var personProto = {
calculateAge:function(){
console.log(2019 -this.yearOfBirth)
},
fullName:function(){
console.log(this.name + ' ' + this.lastName)
}
}
var sam = Object.create(personProto, {
name:{value:'samuel'},
yearOfBirth:{value:1092},
lastName:{value:'max'},
job:{value:'developer'}
});
sam.yearOfBirth = 1992;
console.log(sam.calculateAge());
927
Notice that it now fires an error and tells you exactly what's wrong.
To fix this, just make the properties writable.
'use strict';
var personProto = {
calculateAge:function(){
console.log(2019 -this.yearOfBirth)
},
fullName:function(){
console.log(this.name + ' ' + this.lastName)
}
}
var sam = Object.create(personProto, {
name:{value:'samuel',writable:true},
yearOfBirth:{value:1092,writable:true},
lastName:{value:'max',writable:true},
job:{value:'developer',writable:true}
});
sam.yearOfBirth = 1992;
console.log(sam.calculateAge());
927
The second argument to Object.create uses property descriptors, and the writable attribute defaults to false when you don't specify it.
I would recommend to drop the second argument entirely, and instead use Object.assign to create the properties on the new object:
var sam = Object.assign(Object.create(personProto), {
name: 'samuel',
yearOfBirth: 1092,
lastName: 'max',
job: 'developer',
});
How do I capitalize the first letter of each contact's first name?
String.prototype.capitalize = function (string) { return string.charAt(0).toUpperCase() + string.slice(1);}
var contactList = {};
contactList.bobSmith = {
firstName: "bob",
lastName: "smith",
location: "new york"
};
contactList.johnDoe = {
firstName: "john",
lastName: "doe",
location: "san francisco"
};
var contact2 = contactList["johnDoe"].firstName;
contact1.capitalize();
contact2.capitalize();
console.log(contact1 + " " + contact2);
I get an error message that says "Uncaught TypeError: Cannot read property 'charAt' of undefined".
function () { return this.charAt(0).toUpperCase() + this.slice(1);}
Your String.prototype.capitalize function requires an argument string which you're not passing to it when called. I'd recommend avoiding adding methods to the String.prototype and just use a stand-alone function instead...
function capitalise(str) {
return str.slice(0,1).toUpperCase() + str.slice(1);
}
contact2 = capitalise(contact2);
//=> "John"
...or if you're looking to capitalise the value in the contactList object, then just...
contactList.johnDoe.firstName = capitalise(contact2);
Hope that helped. :)
I wouldn't make it a habit of popping methods into prototype. It's frowned upon because future versions of JavaScript could come out and put a method with the same name in the prototype and you would be screwed.
I didn't write it this way but you could put the capitalize function on to the contactList object and you would basically achieve the same thing your trying to do with the prototype.
var contactList = {};
contactList.names =[
{
firstName: "bob",
lastName: "smith",
location: "new york"
},
{
firstName: "john",
lastName: "doe",
location: "san francisco"
}];
function capitalizeName(name) {
var strCap = name[0].toUpperCase();
var tailTxt = name.substr(1, name.length);
fullTxt = strCap + tailTxt;
return fullTxt;
)
$.each( contactList.names, function(i){
capitalizeName(contactList.names[i].firstName);
});
I've been looking at sailsjs lately and by looking at the documenation http://sailsjs.org/#/documentation/concepts/ORM/Models.html
especailly this:
// From api/models/Person.js...
module.exports = {
attributes: {
// Primitive attributes
firstName: {
type: 'string',
defaultsTo: ''
},
lastName: {
type: 'string',
defaultsTo: ''
},
// Associations (aka relational attributes)
spouse: { model: 'Person' },
pets: { collection: 'Pet' },
// Attribute methods
getFullName: function (){
return this.firstName + ' ' + this.lastName;
},
isMarried: function () {
return !!this.spouse;
},
isEligibleForSocialSecurity: function (){
return this.age >= 65;
},
encryptPassword: function () {
}
}
};
It seems like heavy sql minded, how can I have attributes that are array of objects? like mongodb.
For example maybe my Person model has an attribute called liked_movies which is an array of movies that each movie has its own name and length
You should use model associations.
Salis docs about associations
In the example of the movies you are asking, a many to many association will do. Sails will create the pivot table for you. It will also create a REST like url for that association. i.e person/:personId/likedMovies