Problem
When a child model is initialized for the first time, only defaults of the child are set as attributes.
When a second(and all subsequent) child is being initialized, the attributes of child display defaults of child and it's parent.
Fiddle
var Parent = Backbone.Model.extend({
defaults: {
name: "john",
lname: "smith",
age: 30,
language: "english",
location: "belgium"
}
});
var Child = Parent.extend({
defaults: {
hobby: "doing nothing",
age: 24,
occupation: "student"
},
initialize: function () {
this.constructor.__super__.initialize.apply(this, arguments);
_.defaults(this.defaults, this.constructor.__super__.defaults);
console.log(this.attributes);
}
});
attributes of child initialized for the first time :
var child1 = new Child();
child1.attributes :
hobby: "doing nothing"
age: 24
occupation: "student"
attributes of same Child class, initialized for the second time:
var child2 = new Child();
child2 attributes:
age: 24
hobby: "doing nothing"
language: "english"
lname: "smith"
location: "belgium"
name: "john"
occupation: "student"
Question
Why are not all defaults(child's and parent's) are being set as attributes when a child model is initialized for the first time ?
Because i've to display a Backbone.Collection inside a <ul> and every model's attributes are configurable through a html form inside each <li>. But because of this problem, i can't get to all attributes of the first model in the collection.
You're modifying the Child class's defaults object when the first object is instantiated, during its initialize method. At that point, the Backbone.Model constructor has already used defaults to fill in the attributes for that object, so it will only affect subsequent instantiations.
Take a look at Backbone.Model:
var Model = Backbone.Model = function(attributes, options) {
var defaults;
var attrs = attributes || {};
options || (options = {});
this.cid = _.uniqueId('c');
this.attributes = {};
_.extend(this, _.pick(options, modelOptions));
if (options.parse) attrs = this.parse(attrs, options) || {};
if (defaults = _.result(this, 'defaults')) {
attrs = _.defaults({}, attrs, defaults);
}
this.set(attrs, options);
this.changed = {};
this.initialize.apply(this, arguments);
};
initialize is the very last step, after the defaults have been set, so modifying defaults at that point won't do anything for the current object.
To get it to work how you want, modify defaults after you declare the class, rather than during initialize:
Child.prototype.defaults = _.defaults(Child.prototype.defaults, Parent.prototype.defaults);
Working example
Related
I am having a little problem when trying to add a new object to an array of objects which is stored in state.
Below is a example of the object which is stored in state (scratchTickers):
id : uuid.v1(),
area : 'Scratch',
name : 'Untitled',
status : "",
size : "",
created : {
name : "username",
time : new Date()
},
modified : {
name : "username",
time : new Date()
},
content: [{
content: "Dummy content",
TowerRed: "",
TowerText: "Headlines"
}]
I need to dynamically add new objects in the content array, an object is structured like this:
{
content: "Dummy content",
TowerRed: "",
TowerText: "Headlines"
}
I am trying to use React's immutability helpers to accomplish this but It doesn't seem to be working as it should.
here is my function to add a new object to the content array and update the state:
_createTickerItem: function (id) {
var tickerID = id;
var key = null;
for (var i = 0; i < this.state.scratchTickers.length; i++) {
if(this.state.scratchTickers[i].id == tickerID) {
var ticker = this.state.scratchTickers[i];
var key = i;
}
}
var blankContent = ({
content: "Ticker Content",
TowerRed: "",
TowerText: "Headlines"
});
var oldContent = this.state.scratchTickers[key];
var newContent = update(oldContent, {
content : {
$push : [blankContent]
}
});
this.setState({
scratchTickers: newContent
});
},
So when i clicl the button tun run the _createTickerItem function the component re-renders and the main object has disappeared entirely.
Edit* Ok so I am now 1 step further making sure to set 'newContent' as an array during set state now adds new objects to the content array and renders them correctly.
this.setState({
scratchTickers: [newContent]
});
However if there were multiple objects in state, this will replace them all with juts a single object. So how would it work if I had say to parent objects in state but wanted to just modify the content array of object 1?
Edit 2: i should add that scratchTickers is an array of objects, so the for loop is needed to make sure i modify the correct object.
You're replacing the entire scratchTickers array with only the last updated object.
You have to update the entire array first.
var updates = {};
updates[key] = { $set: newContent }
var newScratchTickers = update(this.state.scratchTickers, updates);
this.setState({
scratchTickers: newScratchTickers
});
I made a question earlier which is mostly confused nonsense
(Mongoose, array in object)
Now I Think I have narrowed it down to the following:
I have a list of drivers which has an Array of cars:
var driverSchema = new mongoose.Schema({
driver: String,
age: String,
cars: [{ type: Schema.Types.ObjectId, ref: 'Car' }]
});
module.exports = mongoose.model('Driver', driverSchema);
A driver-object can be selected to ChoosenDriver like so:
this.setState({choosenDriver:driver})
The problem is that the object Changes when selected. It changes from this:
drivers: array[3]
->0: {}
->1: {}
_v: 0
_id: "4242594395935934"
name: "Roger"
cars: Array[1]
to this:
choosenDriver: {..}
_v:0
_id: "235345353453"
name: "Roger"
cars:
->_proto_ :{..}
0: "4242594395935934",
_id: undefined
cars is no longer an Array when the driver is selected.
Anyone run into something similar maybe?
Update:
I pass the list of drivers to a Child Component like this:
<DriversList drivers={this.props.drivers}
In DriversList i select a driver like this:
(render-func should be enough to show you)
handleDriverClick: function(i) {
this.props.setChoosenDriver(this.props.drivers[i])
},
render: function(){
var self = this;
var drivers = this.props.drivers.map(function(driver,i){
return <li key={driver._id} onClick={self.handleDriverClick.bind(null, i)}> {driver.name} </li>;
});
And in the Parent :
setChoosenDriver: function(driver) {
this.props.setChoosenDriver(driver)
},
And finally in the GrandParent i set the state:
setChoosenDriver: function(driver) {
this.setState({choosenDriver:driver})
},
Update:
getInitialState: function() {
return {
drivers:[]
};
},
componentWillMount: function(){
var self = this;
request
.get(Driversurl)
.end(function(err, res){
self.setState({drivers: res.body});
});
},
Its an Array of drivers wtih objects like:
{"_id":"5607b0747eb3eefc225aed61","name":"Moore","__v":0,"cars":["5607b0747eb3eefc225aed61","5607b07a7eb3eefc225aed62","5606bf4b0e76916c1d1668b4","5607b07a7eb3eefc225aed62"]}
don't execute your request in componentWillMount this is really wrong , execute it in componentDidMount, see this for more details : bind(this) not working on ajax success function
Also I would parse you request result when you receive it, your problem may come from here
self.setState({drivers:JSON.parse(res.body)});
So I have a model in Ember that is generating a hash with three objects. One of the objects is an array of objects with another array inside each object. I need to sort this innermost array, but I am having trouble doing so.
Here are my models.
App.Person = DS.Model.extend ({
first_name: DS.attr('string'),
last_name: DS.attr('string'),
age: DS.attr('string'),
gender: DS.attr('string'),
innerMostArray: DS.hasMany('innerMostObject')
});
App.innerMostObject = DS.Model.extend ({
person_id: DS.belongsTo('person'),
attr1: DS.attr('string'),
attr2: DS.attr('string')
});
Here is my Route
App.NestedArrayRoute = Ember.Route.extend({
model: function(params) {
return Ember.RSVP.hash({
object1: this.store.find('object1', params.object1_id),
people: this.store.all('person'),
object3: this.store.all('object3')
});
},
afterModel: function(model, transition) {
model.people.forEach(function(item, index, enumerable){
var innerMostArray = item.get('innerMostArray');
var sortedArray = innerMostArray.sortBy('attr1', 'attr2');
});
model.people.update();
}
});
I know that I am nowhere near doing this right but I just don't know how to sort this nested array. I've seen examples of array controllers, but I don't know how to use one to sort this nested array. If anyone could give an example of how to do this it would be very helpful. Thank you.
I agree with Kalmans answer, but I suggest you do this sorting with built-in methods to save you trouble:
App.Person = DS.Model.extend({
name: DS.attr('string'),
fruits: DS.hasMany('fruit', {async: true}),
fruitSorting: ['title', 'color'],
sortedFruits: Ember.computed.sort('fruits', 'fruitSorting')
});
I forked his example here: http://emberjs.jsbin.com/manutu/1/edit?html,js,output
One way to do this is to create a computed property on the model as follows:
App.Person = DS.Model.extend({
name: DS.attr('string'),
fruits: DS.hasMany('fruit', { async: true }),
sortedFruits: function(){
var fruits = this.get('fruits');
return fruits.sortBy('title', 'color');
}.property('fruits.#each.title', 'fruits.#each.color')
});
Working example here
I'm working in a collection that contains a model with collections of "itself". For example:
[{
id: 1
name: "John",
children: [
{
id: 32
name: "Peter",
children: []
},
{
id: 54
name: "Mary",
children: [
{
id:12,
name: "Kevin"
}
]
},
]
}]
Let say that I want to get the Kevin "user" by its Id. But all that I have is the "first collection". How can I do that?? And about setting a user within a collection? Another thing: Its possible to get all the Kevin "parents" from him? Like Mary and John?
Does anyone has come to a issue like that?
Thanks a LOT
Well I've made a recursive function on the User's Collection that seems to solved the problem for now ( the best of this is that I can use for retrieve a "deep" model and change it.). Something like that ( if someone has any suggestions, be free to give it a opinion ):
findUserById: function(id) {
var self = new Backbone.Collection(this.toJSON());
return thisCollection(id, this);
function thisCollection(id, collection, array) {
var innerSelf = collection || this;
var thisArray = array || [];
for(var i = innerSelf.models.length; i--;) {
if(innerSelf.models[i].get('id') == id) {
return [innerSelf.models[i]].concat([thisArray]);
}else {
if(innerSelf.models[i].get('children').length > 0) {
thisArray.push(innerSelf.models[i]);
return thisCollection(id, innerSelf.models[i].get('children'), thisArray);
}else {
innerSelf.remove(innerSelf.models[i]);
return thisCollection(id, self, []);
}
}
}
}
}
Basically I return an array with 2 items. The first is the record that I'm looking for and the second is an array with the parents of this user.
Underscore (which is a Backbone dependency, so you already have it) is great for this sort of thing; if you use its "map" function (which Backbone provides as a method on Collection) with its find function, you can do the following:
findPersonInPeopleCollection: function(nameWeAreLookingFor) {
function findChildren(person) {
if (!person.children) return [person];
var children = _.map(person.children, function(child) {
foundPeople.push(findChildren(child);
})
return _.flatten(children);
}
var allPeople = _.flatten(myCollectionOfPeople.map(findChildren));
return _(allPeople).find(function(person) {
return person.get('name') == nameWeAreLookingFor;
}
}
If you wanted to store the parents initially you could either add logic to your "Person" model class's initialize function, eg.
var Person = Backbone.Model.extend({
initialize: function() {
_.each(this.get('children'), function(child) {
child.parent = this;
}, this);
}
});
You could also do something similar by overriding your collection's add method, or adding an event handler to it that triggers after people get added.
Let's suppose I have the following model (model1) and collection (collection1)
model1.attributes = {
name: 'bar'
};
collection1.models = [{}, {}, {}];
It will be possible by using backbone relation to make the model1 to know about the length of collection1?
model1.attributes = {
name: 'bar',
collection1Length = 3 // collection1.models.length
}
Thanks
Based on your comments, it might be best to simply create a reference to the collection itself within the model:
ModelName = Backbone.Model.extend({
...
linked_collection: null // don't call this 'collection', as model.collection already exists
...
}
var model1 = new ModelName();
model1.set('linked_collection',collection1);
Now you can do this at any time to get the linked collection's length.
model1.get('linked_collection').length