Backbone-Relational: Fetch returns generic JS object - backbone.js

In the following code, I establish 3 has-many/belongs-to relations.
Category > Subcategories > Items
Category.js.coffee:
class App.Models.Category extends Backbone.RelationalModel
relations: [{
type: Backbone.HasMany
key: 'subcategories'
relatedModel: 'App.Models.Subcategory'
collectionType: 'App.Collections.Subcategories'
reverseRelation: {
key: 'category',
includeInJSON: 'id'
}
}]
App.Models.Category.setup() # Set up BB Relational
Subcategory.js.coffee:
class App.Models.Subcategory extends Backbone.RelationalModel
relations: [{
type: Backbone.HasMany
key: 'items'
relatedModel: 'App.Models.Item'
collectionType: 'App.Collections.Items'
reverseRelation: {
key: 'subcategory',
includeInJSON: 'id'
}
}]
App.Models.Subcategory.setup() # Set up BB Relational
Item.js.coffee
class App.Models.Item extends Backbone.RelationalModel
initialize: ->
...
App.Models.Item.setup() # Set up BB Relational
Problem:
Calling item.get('subcategory') works as expected, returning a Backbone RelationalModel object. However, for some reason calling category returns a generic JS object.
item.get('subcategory').get('category')
Returns: Object {id: 1, title: "the title"}
In case it's related, console.log #subcategory.relations shows the message "collectionKey=subcategory already exists on collection=true ".

Solved!
Backbone-Relational addresses an issue with coffeescript extends syntax by using the setup() methods as shown above.
My problem here was that my Category.js.coffee was being initialized before my Item.js.coffee so the setup() call's reverse relation could not be added to the model.
To fix this, I moved all of the setup() calls to my backbone initializer (once all objects were defined) in order of relation dependencies:
window.App =
init: (options) ->
# Set up BB Relational
GearSwap.Models.Item.setup()
GearSwap.Models.Category.setup()
GearSwap.Models.Subcategory.setup()

Related

How do I override the url field when extending a base model in Extjs?

I am new to Extjs and am following an Extjs 5 example on creating a model hierarchy:
(http://docs.sencha.com/extjs/5.0/core_concepts/data_package.html)
I created a base model which holds a proxy with a url field. I now want to extend this base model and only override the url part below:
Ext.define('MyPortal.model.Base', {
extend: 'Ext.data.Model',
fields: [{
name: 'id',
type: 'int'
}],
schema: {
namespace: 'MyPortal.model', // generate auto entityName
proxy: {
type: 'ajax'
,url : '/portal-web/{entityName}'
,reader: {
type:'json',
rootProperty:'{entityName:lowercase}',
idProperty: 'id'
}
}
}
});
Here is a child model:
Ext.define('MyPortal.model.Account', {
extend : 'MyPortal.model.Base'
,fields: [
{name: 'accountId', type: 'string'},
{name: 'name', type: 'string'}
]
, //add something here to override the url from the base model above?
});
I tried adding a url field or function to the child model, but these are ignored. Is it possible for a child model to override the parent's url param?
Thanks!
The proxy option in the schema is what Ext5 calls an ObjectTemplate. Simply put, it is used as a default configuration for the models of the schema.
You can override this defaults in the proxy configuration of the model itself (one proxy instance will be created by model class -- not by model instance). Note that the model class itself doesn't accept an url option, it must be set in the proxy.
For you, that would give something like this:
Ext.define('MyPortal.model.Account', {
extend : 'MyPortal.model.Base' // I guess you had a typo here
,fields: [
{name: 'accountId', type: 'string'},
{name: 'name', type: 'string'},
]
// Override proxy URL (the other options of the schema's proxy
// will be used)
,proxy {
url: 'path/to/accounts'
}
});
FYI, the reader doesn't have an idProperty option (so says the docs), it's in the model this time...
I have a slightly different problem: my data model is generated, so i am using override: to extend model classes like this:
Ext.define('app.override.Foo', {
override: 'app.data.Foo',
proxy: {url: 'rest/foo',type: 'rest'}
}
this proxy override does not take effect in extjs 5 (works great in 4.x)

ExtJS 4 I don't understand models + associations

I don't think the documentation is very clear on this - at least I can't figure out how associations work with models in ExtJS 4. Lets look at a simple example:
the models
Ext.define('app.model.Goo', {
...
fields: ['id', 'foo_id', 'goo_field'],
belongsTo: 'Foo'
});
Ext.define('app.model.Foo', {
...
fields: ['id', 'foo_field'],
hasMany: {model: 'Goo', name: 'goos'} //
});
this allows me to easily write a server response that returns nested data such as:
{
success: true,
foo: {
id: 42
foo_field: 'bacon',
goos: [
{ id: 0, goo_field: 'velociraptor' },
{ id: 1, goo_field: 'spidermonkey' },
...
]
}
}
and parse out the data into their respective models. But what if I want to load nested data lazily? Say I write my server such that it doesn't send any goos field in my returned foo object. What does it mean to write foo.goos().load()? What's being sent to my server then? GET <proxy:url>/<'id' of foo>/goos ?
If you want to lazy load goo you should not send goo in the foo response. But instead you call foo.goos(). This wil return a goo store with a filtervalue foo_id on 42 (primary id from your foo instance). Basicly its doing something like this for you:
Ext.create('Ext.data.Store', {
model: 'app.model.Goo',
filters: [
{
property: 'foo_id',
value: 42
}
]
});
So calling load does the request. Assuming you are using REST proxy, your goo proxy will do the following request: GET <proxy:url>with filter-queryparameters: filter:[{"property":"foo_id","value":42}].
Also I believe you need to specify fully qualified class name (ie. model: 'app.model.Goo').

Fire a remove event on fetching a collection

This is an odd behavior I think. I have two "section" instances. Each one with an exercises collection. Then, I do a fetch for each collection and here is the problem. From server can I to receive some model that can be in the two collections at the same time. But this wouldn't be a problem because they are independent instances.
Model:
class App.Models.Section extends Backbone.RelationalModel
relations: [
{
type: Backbone.HasMany
key: 'exercises'
relatedModel: 'App.Models.Exercise'
collectionType: 'App.Collections.Exercises'
reverseRelation:
key: 'section'
includeInJSON: false
}
]
View:
class App.Views.Section extends Backbone.Views
initialize: ->
#collection.bind 'add', #renderExercise
#collection.bind 'remove', #unrenderExercise
#subviews = {}
renderExercise: (exercise) =>
view = new Baskeitor.Views.ExerciseShow model: exercise
#subviews{exercise.cid} = view
#$el.append view.render().el
unrenderExercise: (exercise) =>
#subviews{exercise.cid}.remove()
delete #subviews{exercise.cid}
Two instances:
section1 = new App.Models.Section
section2 = new App.Models.Section
Fetch in the two exercises collection:
section1.get('exercises').fetch({ data: params, remove:false })
section2.get('exercises').fetch({ data: params, remove:false })
I lied, this is my problem with Backbone. In a first time the collections receive their models and I generated a view for each model (an event 'add', so I render the exercise view). But next, for some reason than I don't understand, Backbone trigger a remove event and removes all models repeated. In resume, only I can have in the collections whose models aren't in the other.
EDIT
I have identified the problem. The matter is that ids are duplicates. If I change their ids manually, then all works fine. But otherwise it doesn't do it. But I think this don't have sense because I am instanciating two differents sections. Each section would have its own array with the ids of exercises.
Finally I have just remove Backbone-Relational from my project.

Backbone-relational between two models

By using Backbone-relational I would like to have the model task in this way:
task = {
id: 1
assigned_id: 2
name: 'read a book',
user: userModel
};
I did try this way (1) but the result is the following (2).
Any ideas?
(1)
var User = Backbone.RelationalModel.extend({
// urlRoot
});
var Task = Backbone.RelationalModel.extend({
relations: [
{
type: Backbone.HasOne,
key: 'user',
relatedModel: User
}
],
// urlRoot
});
(2)
task = {
id: 1
assigned_id: 2
name: 'read a book',
user: null // null instead of having something related to user model
};
Not sure what your the exact JSON is for your Task model, so I'm guessing here.
Backbone-relational is expecting either a fully nested model:
task = {
id: 1
assigned_id: 2
name: 'read a book',
user: {
name: 'Fred Rogers',
id: 42,
occupation: 'Hero'
}
};
Or a string/number, which it will assume to be the id of the related model:
task = {
id: 1
assigned_id: 2
name: 'read a book',
user: 42
};
I'm guessing you're hitting the second case, based on the null value you're getting for the user model.
When backbone-relational instantiates an instance of a model, and the related model is a "key" string/number, it will search its internal store of models to try to find a matching model. If it finds it, it sets that model as the value for the user property.
If it cannot find the model, it stashes the key in the model's relevant relation property model._relations[n].keyContents, and sets the user value to null.
It is at this point that you would use the fetchRelated function to get the related model from the datastore/API.
So, try calling task.fetchRelated() to get the related user model:
task.fetchRelated('user');

extjs Event to update a Grid

I need a grid built in extjs designer to add data to the grid on an event.
so I should have a function that when called with receivedMsg as Args, sends the array data to be added as a new record in the grid.
I do not want it to go out and refresh a json file, not very network friendly.
I have written the server backend myself, and implemented websockets to generate the receivedMsg event.
How can I do this?
Here is where the event should go:
/*
* File: app/view/MyGridPanel.js
* Date: Sat Jan 14 2012 14:58:07 GMT-0500 (Eastern Standard Time)
*
* This file was generated by Ext Designer version 1.2.2.
* http://www.sencha.com/products/designer/
*
* This file will be generated the first time you export.
*
* You should implement event handling and custom methods in this
* class.
*/
Ext.define('MyApp.view.MyGridPanel', {
extend: 'MyApp.view.ui.MyGridPanel',
initComponent: function() {
var me = this;
me.callParent(arguments);
}
});
[
["Ace Supplies", "Emma Knauer", "555-3529"],
["Best Goods", "Joseph Kahn", "555-8797"],
["First Choice", "Matthew Willbanks", "555-4954"],
["First Choice", "Matthew Willbanks", "555-4954"]
]
If I understand the question properly, the array that you want to add as a record would first have to be converted into an instance of the same Ext.data.Model that your grid is using. Then you could call your "grid.store.insert()" method. For example, if your grid is using a model called 'Employee' like this:
// Grid data model
Ext.define('Employee', {
extend: 'Ext.data.Model',
fields: [
{name: 'name', type: 'int'},
{name: 'email', type: 'string'},
{name: 'start', type: 'date'},
{name: 'salary', type: 'int'},
{name: 'active', type: 'bool'}
]
});
You could create the model instance outside of the function with your data and just pass that as the function args, or if you can only get the data as an array (hopefully you can set the sequence), you can create the model instance inside the function itself, shown here:
// the function you wanted
addRecord: function(myRecordArray) {
// Create a model instance
var r = Ext.create('Employee', {
name: myRecordArray[0],
email: myRecordArray[1],
start: myRecordArray[2],
salary: myRecordArray[3],
active: myRecordArray[4]
});
// get the grid store and the number of records in it
var store = this.getStore();
var maxIndex = store.getCount();
// adds record to the end of the grid (args are: index, Ext.data.Model[])
store.insert(maxIndex, r)
}
You would probably have to tweak this depending on how your store is set-up but that should get it started.

Resources