Backbone Fetch Related Models - javascript

I'm working on a Backbone app, but everything I've read so far is either about displaying a list of items (a TODO list for example) or a single item.
Right now I have users, each user has a list of skills (pretty much like any game). I can easily get all users or a single user, the same for the skills but what if I want to get all the skills for a given user? How would I do that?
I thought about just adding a property to the users with a new instance of a collection, something like this:
var Users = Backbone.Model.extend({
skills: new Skills({ user: this })
});
var Skills = Backbone.Collection.extend({
model: Skill,
url: '/someUrl',
initialize: function (options) {
// fetch all skills from an user
this.fetch({ data: { user: options.user.get('id') } });
}
});
But I don't have much experience with Backbone and I don't really like the idea of that, also the request would look something like /someUrl?user=1 which I'd rather avoid, /someUrl/user/1 would be much better.
I've also noticed BackboneRelational but I haven't really tried it, it seems a bit of an overkill for my problem, but maybe I'm wrong.
What approach should I take to fetch all of my users skills? Thanks in advance.

I highly recommend you to checkout this post, sure you will find an answer. If short you may have following approach without any additional plugins and build nested model :
expect following json:
{
name: 'Gorbachov',
age: '75',
skills: [
{
name: 'kalashnikov'
},
{
name: 'vodka'
},
{
name: 'balalaika'
}
]
}
lets update User model:
User = Backbone.Model.extend({
initialize: function(){
var skills = this.get("skills");
if (skills){
this.skills = new Skills(skills);
this.unset("skills");
}
}
})
Then create SkillsCollection:
SkillsCollection = Backbone.Collection.extend({
model: Skill
})
and Skill model:
Skill = Backbone.Model.extend({
defaults: {
name: 'unnnamed'
}
})

Related

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.

Getting model id in different models create form in backbone?

i'm working on a project in backbone and are having some problems when trying to create a new model.
I have an application with two different models where one depends on another.
My models are Books and Authors, and to create a book I need the id of the author.
This is where my problem lies, I have no idea how to get this using backbone?
if I'm not precise enough please ask.
Thank you!
How about something like this:
// create Book model
var book = Backbone.Model.extend({
defaults: {
Title: "",
Desc: "",
Author: 0
}
});
// create Author model
var author = Backbone.Model.extend({
defaults: {
Name: "",
ID: 1
}
});
var markTwain = new author({
Name: "Mark Twain"
});
var huckleberryFinn = new book({
Author: markTwain.get("ID")
});
console.log(huckleberryFinn);
I think what you are looking for is a subcollection. You will end up having an authors collection holding the Autors models. And then you might attach a books collection to every author which holds the book models. Untested proposal:
var Books = Backbone.Collection.extend({
initialize: function(options){
this.author = options.author;
var authorid = this.author.get("id");
}
});
var Author = Backbone.Model.extend({
initialize: function(){
this.books = new Books( {author:this} );
}
});

Ember.js: Where does this method go?

Coming to Ember from Rails, one of the places I'm struggling is trying to figure out Ember's definitions of models, views, and controllers.
I'm just testing out some sample Ember code. I'm getting my user events via the GitHub API, and I want to change the type name into something readable.
I have a jsbin here, but here's the gist:
App = Ember.Application.create();
App.IndexRoute = Ember.Route.extend({
model: function(){
return Ember.$.getJSON('https://api.github.com/users/thenickcox/events').then(function(data){
return data.splice(0,7);
});
}
});
I have a method that types a type and returns a string:
interpretType: function(type){
if (type === 'PushEvent') {
return 'Pushed';
}
return name;
}
In Rails, this would go on the model. But the only model here is the one that Ember created in memory by default (right?). So then I thought, it's something that each member of the array needs, because here's the view:
<h3> Some events</h3>
<ul>
{{#each}}
<li>I {{interpretType(type)}} to {{repo.name}}</li>
{{/each}}
</ul>
So is that something that goes on Ember.ArrayController? I tried that, like this:
App.IndexController = Ember.ArrayController.extend({
interpretType: function(type){
if (type === 'PushEvent') {
return 'Pushed';
}
return name;
}.property()
});
That just gave me an error. Where do I put this?
PS. So you don't have to look at the GitHub API, here's an example JSON object:
{
id: "1890853674",
type: "CreateEvent",
actor: {
id: 702327,
login: "thenickcox",
gravatar_id: "63f35d9e50dfd73281126b051a51668a",
url: "https://api.github.com/users/thenickcox",
avatar_url: "https://2.gravatar.com/avatar/63f35d9e50dfd73281126b051a51668a?d=https%3A%2F%2Fa248.e.akamai.net%2Fassets.github.com%2Fimages%2Fgravatars%2Fgravatar-user-420.png&r=x"
},
repo: {
id: 14463966,
name: "thenickcox/whiskey_taster",
url: "https://api.github.com/repos/thenickcox/whiskey_taster"
},
payload: {
ref: "master",
ref_type: "branch",
master_branch: "master",
description: "My first ember/rails app"
},
public: true,
created_at: "2013-11-17T09:00:17Z"
},
Here is an updated JSBin
Basically, the each can specify an itemController to decorate the model.
App.EventController = Ember.ObjectController.extend({
interpretType: function(){
var type = this.get('model.type');
if (type === 'PushEvent') {
type = 'Pushed';
}
return type;
}.property('model.type')
});
Handlebars doesn't have functions as you've written it, but since we are now using the event controller which wraps the single model, we just refer to interpretType to do the translation:
{{#each itemController='event'}}
<li>{{interpretType}} to {{repo.name}}</li>
{{/each}}
Put it inside an Ember.ObjectController
ArrayController's deal with methods related to the collection of data from the model, whereas ObjectController deals with methods related to the specific object.
I'm also learning Ember from a Rails background.
If you haven't already come across this, you will definetely want to check out ember-tools, it's a command line generator very similar to what we've got in rails. I cant imagine building an Ember app without something like it..

backbone.js collections in two levels

So, I'm just started using backbone.js and what I have some troubles understanding how to define two collections where one of them is within a model of the other one.
The REST API looks something like this:
/sites <--- one collection
/sites/123/entities <--- another collection
/sites/123/entities/abc <--- a specific entity within a specific site
This is how the Sites collection is defined:
var ROOT = 'http://localhost:5000';
Site = Backbone.Model.extend({
defaults: {
id: "default",
description: "My site"
}
});
Sites = Backbone.Collection.extend({
model: Site,
url: ROOT + "/sites"
});
How would the Entities collection and model look like to achieve this?
This is how I imagine it would be, though haven't tested it:
Entity Model/Collection:
var ROOT = 'http://localhost:5000';
Entity = Backbone.Model.extend({
defaults: {
id: "default",
description: "My Entity"
}
});
Entities = Backbone.Collection.extend({
model: Entity,
ownUrl: '/entities',
site: {},
url: function() {
return (this.site? this.site.url() : '') + this.ownUrl;
},
initialize: function(options) {
this.site = options.site;
}
});
Site Model/Collection:
Site = Backbone.Model.extend({
defaults: {
id: "default",
description: "My site"
}
});
Sites = Backbone.Collection.extend({
model: Site,
url: ROOT + "/sites",
});
Example of setting up:
// New collection of site
var site1 = new Site({id: 'site1', description: "This is site 1"});
var site2 = new Site({id: 'site2', description: "This is site 2"});
// Add entities to sites
var entityCollection1 = new Entities([
{id: 'entity1'},
{id: 'entity2'}
], {site: site1});
// Collection of sites
var mySites = new Sites([site1,site2]);
Edited:
According to Backbone documents, from version 1.1, this.options is not attached automatically anymore. Sorry I was still refer to the 0.9.x code.
In 1.1, Backbone Views no longer have the options argument attached as
this.options automatically. Feel free to continue attaching it if you
like. http://backbonejs.org/#changelog
For the url, my bad, it was supposed to be this.site.url() because it's a model's function which will take the default value as [collection.url/modelId].
In this case, it should return 'site/1' for Model site1 (i.e. 'site/2') then you append whatever your entity collection url will be.(ex: '/entities').
This makes your Entities collection url for site 1 become: /site/1/entities. Any model in this collection will have url /site/1/entities/[modelId]

Proper method for backbone-relational relationship definitions based on keys

I've been over the docs quite a few times, but this aspect still isn't clear to me. It's entirely possible that I'm thinking backbone-relational does something that it doesn't.
I'm looking for the way to define relationships based on key to avoid all the boilerplate fetching nonsense.
Take the canonical Artists and Albums example:
An artist has many albums as defined by album.artist_id
/api/artist/62351 might return
{
id: 62351,
name:"Jimi Hendrix"
}
similarly /api/album?artist_id=62351 might return
[
{
id:5678,
name: "Are You Experienced?"
artist_id: 62351
},
{
id: 4321,
name: "Axis: Bold as love",
artist_id: 62351
}
]
How might I define Artist and Album relationships such that
var hendrixInstance = new Artist({id:62351});
hendrixInstance.get('albums');
would fetch and return a collection of albums based on the album foreign_key artist_id? It must just be some permutation of key/keySource/keyDestination that I've yet to try, or be a problem that backbone-relational isn't trying to solve, but my doc groking has failed and I think a concise answer to this on SO might help future Googlers.
var Artist = Backbone.RelationalModel.extend({
urlRoot: '/api/artist',
relations:[{
key: 'albums', //Docs say this is the foreign key name, but in practice it doesn't appear that way. Need keySource/Destination?
type: Backbone.HasMany,
reverseRelation: {
key: 'artist',
type: Backbone.HasOne
}
}]
});
var Album = Backbone.RelationalModel.extend({
urlRoot: '/api/album'
});
Bonus points to an example model that references its self adjacency list style with parent_id
So, #xzhang's method above kept me iterating on this problem. First off, I'd love to be proven wrong on this, but I haven't found a way that backbone-relational handles this problem without additional custom code. Since this in my mind is an incredibly basic example of a OneToMany relationship, I'm still holding out hope that I'm just not getting something obvious.
Here's what I ended up doing to handle the situation. Unfortunately it still does not automatically fetch from the server when someobject.fetch('somerelationship') is called, which is what I really want. The parse function won't be necessary for most people, but it's required for the api I'm calling.
First I set up a base collection from which to extend:
var BaseCollection = Backbone.Collection.extend({
initialize: function(models, options) {
if (_.isObject(options.relation)) {
this.url = '/api/'
+ options.relation.keySource
+ '?search.'+options.relation.reverseRelation.keySource
+ '=' + options.foreignId;
}
},
parse: function(res) { return res.success ? res.list : res },
});
Then a reusable helper function (could probably be rolled into BaseCollection) to assist with creating relationships
function collectionOptions(instance) {
return {"relation":this, "foreignId":instance.get('id') };
};
And finally, those relationships are told to use BaseCollection as their CollectionType, and the collectionOptions() helper is assigned to set collectionOptions.
var Form = BaseModel.extend({
urlRoot: '/api/form',
relations:[
{
key: 'fills',
keySource: 'fill',
relatedModel: Fill,
type: Backbone.HasMany,
collectionOptions: collectionOptions,
collectionType: BaseCollection,
reverseRelation: {
key: 'form',
keySource: 'form_id',
type: Backbone.HasOne
}
},{
key: 'children',
keySource: 'form',
relatedModel: 'Form',
type: Backbone.HasMany,
collectionOptions: collectionOptions,
collectionType: BaseCollection,
reverseRelation: {
key: 'parent',
keySource: 'parent_id',
type: Backbone.HasOne
}
}
]
});
This allows me to avoid changing the server side API to return a list of ids and then individually fetch those ids. Instead I can just:
var form = new Form({id:1});
form.get('children').fetch();
form.toJSON(); //now has {id:1, ..., ..., children:[child,child,child,...]}
An extension to autoFetch children on the first call to .get('children') would be just the ticket, but I haven't discovered how to do that without modifying backbone-relational itself.
I am facing the exactly problem (Backbone-relational hasmany best practices), after 2 days research and look into the source code, I don't think key/keySource/keyDestination will do the work (correct me if I am wrong).
So I end up with create my own relation type, so far works fine. This may not a good solution, but hope can help you.
var LazyMany = Backbone.HasMany.extend({
setRelated: function (related) {
var relation = this.options
, instance = this.instance
;
if (related && !_.result(related, 'url')) {
related.url = relation.relatedModel.prototype.urlRoot +
'?' + relation.reverseRelation.key + '=' + instance.id;
}
return LazyMany.__super__.setRelated.apply(this, arguments);
}
});
Then in your model:
var Album = Backbone.RelationalModel.extend({
urlRoot: '/api/album/'
});
var Artist = Backbone.RelationalModel.extend({
urlRoot: '/api/artist/',
relations:[{
key: 'albums',
type: LazyMany,
includeInJSON: false,
relatedModel: Album,
reverseRelation: {
key: 'artist',
// I didn't test this, I don't have artist_id, artist is "id" in my app
keySource: 'artist_id',
keyDestination: 'artist_id',
includeInJSON: 'id'
}
}]
});
So if you don't define a collectionType or your collection don't have a url field, LazyMany will create a collection with url: /api/album/?artist=62351.
Then you just need fetch the collection: artist.get('albums').fetch().
Hope this can help, and I am still looking for better solutions.

Categories