Backbone: How to get model attributes from a collection inside another model? - javascript

Model code:
App.Team = Backbone.RelationalModel.extend({
urlRoot: 'data/json/team',
/*urlRoot: 'data/json/myteam.txt',*/
idAttribute: 'id',
relations:
...
app.currentTeam = new App.Team({id:11});
View:
var trophiesBox = JST['teaminfo/history/leftbox'](app.currentTeam.attributes);
$("#teamhistory_leftbox").append(trophiesBox);
for (var i = 1; i <= app.currentTeam.attributes.history.length; i++) {
var historyData = app.currentTeam.attributes.history.get(i);
var historyRow = JST['teaminfo/history/row'] (historyData.attributes);
$("#teamhistory_table_body").append(historyRow);
}
I'm getting "Uncaught TypeError: Cannot read property 'attributes' of undefined"
on var historyRow = JST['teaminfo/history/row'] (historyData.attributes); line.
Before I had problems defining historyData, probably since it is a model in a collection (app.currentTeam.attributes.history) inside of another model (app.currentTeam). I was getting [Object] (app.currentTeam.attributes.history) doesn't have a 'get' method type of error. Now it passes fine, but I get another error message in the next line, so I wonder what is wrong with my code here.
app.currentTeam.attributes loads fine, so I guess there is a problem retrieving attributes of a model that is inside a collection within another model.
Edit: relation of Team and History collection:
{
type: Backbone.HasMany,
key: 'history',
relatedModel: 'App.HistoryItem',
collectionType: 'App.History',
reverseRelation: {
key: 'team',
includeInJSON: 'id',
}
}

You get Model from Collection from wrong method
app.currentTeam.attributes.history.get(i);
You have to use
app.currentTeam.get('history') // you got collection
app.currentTeam.get('history').at(i); // you got model from collection by index
Update1:
Try use iterator for get elements from collection:
var trophiesBox = JST['teaminfo/history/leftbox'](app.currentTeam.attributes);
$("#teamhistory_leftbox").append(trophiesBox);
app.currentTeam.get('history').each(function(historyData, i) {
var historyRow = JST['teaminfo/history/row'](historyData.attributes);
$("#teamhistory_table_body").append(historyRow);
}, this);

Have you checked Backbone Associations. This might be of some help.

Related

Javascript reset object inside array

I have the below JS code in my Ember app that gets called;
myPanels.accordionPanels = [];
myPanels.accordionPanels.push({
panel: {
name: "my-grid",
type: 'comp',
props: [{
key: 'elementId',
value: "myCustomId"
}]
}
});
So as you can see, I start by setting myPanels.accordionPanels = [] every time and then push the object.
However, I got the following error
Assertion Failed: Attempted to register a view with an id already in
use: myCustomId
So I am assuming that the object inside is not getting reset & it is able to find the earlier created "myCustomId".
Am I resetting the array (or rather the object inside it) correctly ?
Since I am able to push values using:
accordionPanels = [];
accordionPanels.push({
panel: {
name: "my-grid",
type: 'comp',
props: [{
key: 'elementId',
value: "myCustomId"
}]
}
});
make sure myPanels.accordionPanels doesn't have any prototype associated with it.
Try to inspect its value as:
myPanels.accordionPanels = [];
console.log(myPanels.accordionPanels); // see if it has values.
You can delete value using :
delete myPanels.accordionPanels PROTOTYPE

Populate Backbone.js JSON response into nested collections inside nested collections/models

My problem is that I am just starting out with Backbone.js and are having trouble wrapping my head around a complex problem. I want to save a form that have infinite fields, and some of the fields also needs to have infinite options. I'm just worried I might have started at the wrong end with a JSON response, instead of building the models/collections first. Here is a short pseudocode of what I try to achieve.
id:
parent: <blockid>
fields: array(
id:
title:
helpertext
options: array(
id:
type:
value:
)
)
Currently I am working with a faked JSON response from the server, which I built from scratch, and now I want to divide it into models and collections on the client side.
//Fake a server response
var JSONresponse = {
"formid":"1",
"fields":[
{
"fieldid":"1",
"title":"Empty title",
"helper":"Helper text",
"type":"radio",
"options":[
{
"optionid":"1",
"value":"Empty option.."
},
{
"optionid":"2",
"value":"Empty option.."
}
]
},
{
// fieldid2
}
]
};
The idea is to add fields as I see fit, and then if the field type is radio/checkbox/ul/ol there must also be an "options" array within the field.
My work so far:
var app = {};
app.Models = {};
app.Collections = {};
app.View = {};
app.Models.Option = Backbone.Model.extend({
});
app.Collections.Options = Backbone.Collection.extend({
model: app.Models.Option
});
app.Models.Field = Backbone.Model.extend({
options: new app.Collections.Options()
});
app.Collections.Fields = Backbone.Collection.extend({
model: app.Models.Field
});
app.Models.Form = Backbone.Model.extend({
formid : "1",
fields: new app.Collections.Fields(),
initialize: function() {
}
});
How do I split up my JSON response into all these models and collections?
(Perhaps I should re-evaluate my approach, and go for something like form.fieldList and form.optionList[fieldListId] instead. If so, how would that look like?)
Edit: Here is a little jsfiddle after many fixes, but I still don't really know how to make the inner options list work.
The easiest solution would be using Backbone Relational or Backbone Associations.
The documentation should be enough to help you get started.
If you don't want to use a library you could override the parse function on the Form model.
app.Models.Form = Backbone.Model.extend({
defaults: {
fields: new app.Collections.Fields()
},
parse: function(response, options) {
return {
formid: response.formid,
fields: new app.Collections.Fields(_.map(response.fields, function(field) {
if (field.options) {
field.options = new app.Collections.Options(field.options);
}
return field;
}))
};
}
});
Now if you fetch a form from the server, the response will be parsed into an object graph of models and collections.
form.get('fields') will return an app.Collections.Fields collection. form.get('fields').first().get('options') will return an app.Collections.Options collection, if any options exist.
Also, you could create the form model like this:
var form = new app.Models.Form(JSONresponse, {
parse: true
});
This would result in the same object structure.
It's quite hard to handle the case of nested models and collections right in plain Backbone.
Easiest way of handling this will be something like this:
var Option = Nested.Model.extend({
idAttribute : 'optionid',
defaults : {
optionid : Integer
value : ""
}
});
var Field = Nested.Model.extend({
idAttribute : 'fieldid',
defaults : {
fieldid : Integer,
title : "",
helper : "",
type : "radio",
options : Option.Collection
}
});
var Form = Nested.Model.extend({
idAttribute : 'formid',
defaults : {
formid: Integer,
fields: Field.Collection
});
https://github.com/Volicon/backbone.nestedTypes
And that's it. Yep, you'll get direct access to the attributes as free bonus, just form.fields.first().options.first().value, without that get and set garbage.

Merge attributes when using model.set with Backbone Associations

My problem is very specific to Backbone Associations (http://dhruvaray.github.io/backbone-associations). I'm wondering if it's possible to merge attributes when setting properties on nested models. Here is a reduction of the issue:
// define the Layout model
var Layout = Backbone.AssociatedModel.extend();
// define the User model, with layout as a Related model
var User = Backbone.AssociatedModel.extend({
relations: [
{
type: Backbone.One,
key: 'layout',
relatedModel: Layout
}
],
defaults: {
layout: {}
}
});
// create a new user
var user = new User({ user_name: 'pascalpp' });
// set a property on the layout model
user.set('layout.foo', 'bar');
user.get('layout.foo'); // returns 'bar'
// call set on the user directly, passing a JSON structure with no foo property
user.set({ layout: { 'baz': 'bing' } });
user.get('layout.foo'); // foo got wiped, so this returns undefined
The real-world scenario I'm facing is that we need to fetch partial data for a user and set that on the user model without obliterating previously set attributes that don't exist in the current fetch. So I'm hoping we can merge when setting attributes. Is this possible?
Backbone-associations updates existing nested model if id's of new and existing model match. If id's are undefined or they don't match, then the nested model gets replaced by a new one.
What I do for these singleton nested models is I introduce fake id=0 and then it works like expected.
Here is a working jsfiddle.
Working code:
// define the Layout model
var Layout = Backbone.AssociatedModel.extend({
defaults: {
id: 0
}
});
// define the User model, with layout as a Related model
var User = Backbone.AssociatedModel.extend({
relations: [
{
type: Backbone.One,
key: 'layout',
relatedModel: Layout
}
],
defaults: {
layout: {}
}
});
// create a new user
var user = new User({ user_name: 'pascalpp' });
// set a property on the layout model
user.set('layout.foo', 'bar');
user.get('layout.foo'); // returns 'bar'
// call set on the user directly, passing a JSON structure with no foo property
user.set({ layout: { id:0, 'baz': 'bing' } });
user.get('layout.foo'); // foo got wiped, so this returns undefined
alert(user.get('layout.foo'))

Backbone.js pass array to view

I want to pass an array to a view like this
this.account_nav = new AccountNav.View({
views: [
{ ref: new Member.Views.AccountNav({ model: this.model }), id: 'viewA' },
{ ref: new Member.Views.SettingsNav({ model: this.model}), id: 'viewB' }
]
});
However there is an error:
Uncaught Error: The argument associated with selector '' is defined
and a View. Set manage property to true for Backbone.View
instances. backbone.layoutmanager.js:208
pointing to
this.account_nav = new AccountNav.View({
Any ideas why I get this error?
If your View definition is created using Backbone.Layout.extend rather than Backbone.View.extend, this issue shouldn't arise.
To cite the Layout Manager documentation example:
var LoginView = Backbone.Layout.extend({
template: "#login-template"
});
versus
var LoginView = Backbone.View.extend({
// [...]
});
https://github.com/tbranyen/backbone.layoutmanager/wiki/Example-usage#structuring-a-view

Foreign key populated with an object

I would like to make a relation between two models User and Task using backbone-relational.
The relation between the two models is the following:
taskModel.creator_id = userModel.id
// TaskModel
var TaskModel = Backbone.RelationalModel.extend({
relations: [
{
type: Backbone.HasOne,
key: 'creator',
keySource: 'creator_id',
relatedModel: Users
}
],
// some code
});
// Task collection
var TaskCollection = Backbone.Collection.extend({
model: TaskModel,
// some code
});
// User Model
var User = Backbone.RelationalModel.extend({
// some code
});
Actually the problem is in the collection.models, please see the attached images:
Please check this jsfiddle: http://jsfiddle.net/2bsE9/5/
var user = new User(),
task = new Task(),
tasks = new Tasks();
task.fetch();
user.fetch();
tasks.fetch();
console.log(user.attributes, task.attributes, tasks.models);
P.S.:
Actually I am using requireJs to get the UserModel, so I cannot include quotes in relatedModel value.
define([
'models/user',
'backbone',
'relationalModel'
], function (User) {
"use strict";
var Task = Backbone.RelationalModel.extend({
relations: [
{
type: Backbone.HasOne,
key: 'creator',
keySource: 'creator_id',
relatedModel: User
}
],
});
);
Edit 2:
http://jsfiddle.net/2bsE9/13/
I updated the jsfiddle to reflect the changes I suggested below. As long as you are calling toJSON on your task, what gets to the server is a json object with the creator_id property set to the actual id of the user. The keyDestination here is redundant as the documentation states it is set automatically if you use keySource.
Edit:
https://github.com/PaulUithol/Backbone-relational#keysource
https://github.com/PaulUithol/Backbone-relational#keydestination
https://github.com/PaulUithol/Backbone-relational#includeinjson
The combination of the three above might solve your issue.
var Task = Backbone.RelationalModel.extend({
relations: [
{
type: Backbone.HasOne,
// The User object can be accessed under the property 'creator'
key: 'creator',
// The User object will be fetched using the value supplied under the property 'creator_id'
keySource: 'creator_id',
// The User object will be serialized to the property 'creator_id'
keyDestination: 'creator_id',
// Only the '_id' property of the User object will be serialized
includeInJSON: Backbone.Model.prototype.idAttribute,
relatedModel: User
}
],
});
The documentation also states that the property specified by keySource or keyDestination should not be used by your code. The property cannot be accessed as an attribute.
Please try this and comment if that fixes your issue.
Btw, here is a nice blog post that uses backbone-relational end to end.
http://antoviaque.org/docs/tutorials/backbone-relational-tutorial/
Edit
Updated jsfiddle
The problem is that Backbone-Relational explicitly deletes the keySource to 'prevent leaky abstractions'. It has a hardcoded call to unset on the attribute, in Backbone-Relational:
// Explicitly clear 'keySource', to prevent a leaky abstraction if 'keySource' differs from 'key'.
if ( this.key !== this.keySource ) {
this.instance.unset( this.keySource, { silent: true } );
}
You will need to overwrite the unset method in your Task model:
var Task = Backbone.RelationalModel.extend({
urlRoot: ' ',
relations: [
{
type: Backbone.HasOne,
key: 'creator',
relatedModel: User,
keySource: 'creator_id'
}
],
unset: function(attr, options) {
if (attr == 'creator_id') {
return false;
}
// Original unset from Backbone.Model:
(options || (options = {})).unset = true;
return this.set(attr, null, options);
},
sync: function (method, model, options) {
options.success({
id: 1,
name: 'barTask',
creator_id: 1
});
}
});
Obvious problems with this approach are that you will need to modify your code if either Backbone changes its Backbone.Model.unset method or Backbone-Relational changes its keySource behavior.

Categories