Backbone collection reset and parse - javascript

I am rendering a view in my backbone application when a collection is reset,
initialize: function(options) {
this.options = options;
this.model.get('projects').fetch({
data: {
organisation_id: this.model.get('id')
}
}, {reset:true});
this.model.get('projects').on('reset', this.render, this);
this.model.get('projects').on('add', this.addNewProject, this);
this.model.get('projects').on('sort', this.addProjects, this);
},
render: function() {
console.log(this.model.get('projects').state);
this.$el.html( this.template({
is_owner: this.options.is_owner,
className: this.options.className,
pagination: this.model.get('projects').state
}));
this.addProjects();
this.filter = new Pops.Views.OrganisationProjectsFilter({
el:this.$el.find('.div-organisation-filter-wrapper'),
model : this.model,
collection: this.collection
});
this.filter.render().el;
return this;
},
As you can see I run a fetch and the reset my collection to the fetched data. My problem is that in the view I am trying to use some values that come from the server, and it appears they are null, here is my collection,
App.Collections.PaginatedProjects=
Backbone.PageableCollection.extend({
url: App.API_ROOT + "/projects/paginated",
// Initial pagination states
state: {
pageSize: 2,
sortKey: "name",
order: 1,
totalRecords:null
},
// You can remap the query parameters from `state` keys from
// the default to those your server supports
queryParams: {
totalPages: null,
totalRecords: null,
sortKey: "sort",
},
// get the state from Github's search API result
parseState: function (resp, queryParams, state, options) {
this.state.totalRecords = resp.total;
this.state.totalPages = resp.total / this.state.pageSize;
this.state.lastPage = this.state.totalPages;
},
// get the actual records
parseRecords: function (resp, options) {
return resp.data;
}
});
As you can see I am running the parse functions to retrieve and set to values, if I console my collection I can see the correct values, but when I try and use them in my view they are null, am I using parse or reset wrong or maybe both?

I think this is because when you render the data didn't arrive yet. Try this:
this.model.get('projects').fetch({
success: function(model,response) {
var data = //some of your answer
}
});

From what I understand, you did a
console.log(this.model.get('projects').state);
and found out it is print out correctly but then used in the
this.template({
is_owner: this.options.is_owner,
className: this.options.className,
pagination: this.model.get('projects').state
})
then it does not appear? If this is the case, I don't think it's a problem in Backbone Collection. I think it has something to do with how you access the data in your template.
If you are using handlebars, because this "state" variable is a json,
you maybe using
{{pagination.pageSize}}
in the hbs file to print it out. I have a heizy memory that it was not supported a while ago. try using
{{pagination/pageSize}}
Also, if this snippet is inside a {{#if}} or a {{#each}} etc, according to the handlebars documentation, you may need to go in a scope. use
{{../pagination/pageSize}} or {{../../pagination/pageSize}}
I ran into the problem a while ago and did a lot of search to found out about this. Hope it helps

Related

Ember Data belongsTo async relationship omitted from createRecord() save() serialization

Edit 11/16/14: Version Information
DEBUG: Ember : 1.7.0 ember-1.7.0.js:14463
DEBUG: Ember Data : 1.0.0-beta.10+canary.30d6bf849b ember-1.7.0.js:14463
DEBUG: Handlebars : 1.1.2 ember-1.7.0.js:14463
DEBUG: jQuery : 1.10.2
I'm beating my head against a wall trying to do something that I think should be fairly straightforward with ember and ember-data, but I haven't had any luck so far.
Essentially, I want to use server data to populate a <select> dropdown menu. When the form is submitted, a model should be created based on the data the user chooses to select. The model is then saved with ember data and forwarded to the server with the following format:
{
"File": {
"fileName":"the_name.txt",
"filePath":"/the/path",
"typeId": 13,
"versionId": 2
}
}
The problem is, the typeId and versionId are left out when the model relationship is defined as async like so:
App.File = DS.Model.extend({
type: DS.belongsTo('type', {async: true}),
version: DS.belongsTo('version', {async: true}),
fileName: DS.attr('string'),
filePath: DS.attr('string')
});
The part that is confusing me, and probably where my mistakes lie, is the controller:
App.FilesNewController = Ember.ObjectController.extend({
needs: ['files'],
uploadError: false,
// These properties will be given by the binding in the view to the
//<select> inputs.
selectedType: null,
selectedVersion: null,
files: Ember.computed.alias('controllers.files'),
actions: {
createFile: function() {
this.createFileHelper();
}
},
createFileHelper: function() {
var selectedType = this.get('selectedType');
var selectedVersion = this.get('selectedVersion');
var file = this.store.createRecord('file', {
fileName: 'the_name.txt',
filePath: '/the/path'
});
var gotDependencies = function(values) {
//////////////////////////////////////
// This only works when async: false
file.set('type', values[0])
.set('version', values[1]);
//////////////////////////////////////
var onSuccess = function() {
this.transitionToRoute('files');
}.bind(this);
var onFail = function() {
this.set('uploadError', true);
}.bind(this);
file.save().then(onSuccess, onFail);
}.bind(this);
Ember.RSVP.all([
selectedType,
selectedVersion
]).then(gotDependencies);
}
});
When async is set to false, ember handles createRecord().save() POST requests correctly.
When async is true, ember handles GET requests perfectly with multiple requests, but does NOT add the belongsTo relationships to the file JSON during createRecord().save(). Only the basic properties are serialized:
{"File":{"fileName":"the_name.txt","filePath":"/the/path"}}
I realize this question has been asked before but I have not found a satisfactory answer thus far and I have not found anything that suits my needs. So, how do I get the belongsTo relationship to serialize properly?
Just to be sure that everything is here, I will add the custom serialization I have so far:
App.ApplicationSerializer = DS.RESTSerializer.extend({
serializeIntoHash: function(data, type, record, options) {
var root = Ember.String.capitalize(type.typeKey);
data[root] = this.serialize(record, options);
},
keyForRelationship: function(key, type){
if (type === 'belongsTo') {
key += "Id";
}
if (type === 'hasMany') {
key += "Ids";
}
return key;
}
});
App.FileSerializer = App.ApplicationSerializer.extend(DS.EmbeddedRecordsMixin, {
attrs: {
type: { serialize: 'id' },
version: { serialize: 'id' }
}
});
And a select:
{{ view Ember.Select
contentBinding="controller.files.versions"
optionValuePath="content"
optionLabelPath="content.versionStr"
valueBinding="controller.selectedVersion"
id="selectVersion"
classNames="form-control"
prompt="-- Select Version --"}}
If necessary I will append the other routes and controllers (FilesRoute, FilesController, VersionsRoute, TypesRoute)
EDIT 11/16/14
I have a working solution (hack?) that I found based on information in two relevant threads:
1) How should async belongsTo relationships be serialized?
2) Does async belongsTo support related model assignment?
Essentially, all I had to do was move the Ember.RSVP.all() to after a get() on the properties:
createFileHelper: function() {
var selectedType = this.get('selectedType');
var selectedVersion = this.get('selectedVersion');
var file = this.store.createRecord('file', {
fileName: 'the_name.txt',
filePath: '/the/path',
type: null,
version: null
});
file.set('type', values[0])
.set('version', values[1]);
Ember.RSVP.all([
file.get('type'),
file.get('version')
]).then(function(values) {
var onSuccess = function() {
this.transitionToRoute('files');
}.bind(this);
var onFail = function() {
alert("failure");
this.set('uploadError', true);
}.bind(this);
file.save().then(onSuccess, onFail);
}.bind(this));
}
So I needed to get() the properties that were belongsTo relationships before I save the model. I don't know is whether this is a bug or not. Maybe someone with more knowledge about emberjs can help shed some light on that.
See the question for more details, but the generic answer that I worked for me when saving a model with a belongsTo relationship (and you specifically need that relationship to be serialized) is to call .get() on the properties and then save() them in then().
It boils down to this:
var file = this.store.createRecord('file', {
fileName: 'the_name.txt',
filePath: '/the/path',
type: null,
version: null
});
// belongsTo set() here
file.set('type', selectedType)
.set('version', selectedVersion);
Ember.RSVP.all([
file.get('type'),
file.get('version')
]).then(function(values) {
var onSuccess = function() {
this.transitionToRoute('files');
}.bind(this);
var onFail = function() {
alert("failure");
this.set('uploadError', true);
}.bind(this);
// Save inside then() after I call get() on promises
file.save().then(onSuccess, onFail);
}.bind(this));

Not fetching correct url issue

I have a backboneJS app that has a router that looks
var StoreRouter = Backbone.Router.extend({
routes: {
'stores/add/' : 'add',
'stores/edit/:id': 'edit'
},
add: function(){
var addStoresView = new AddStoresView({
el: ".wrapper"
});
},
edit: function(id){
var editStoresView = new EditStoresView({
el: ".wrapper",
model: new Store({ id: id })
});
}
});
var storeRouter = new StoreRouter();
Backbone.history.start({ pushState: true, hashChange: false });
and a model that looks like:
var Store = Backbone.Model.extend({
urlRoot: "/stores/"
});
and then my view looks like:
var EditStoresView = Backbone.View.extend({
...
render: function() {
this.model.fetch({
success : function(model, response, options) {
this.$el.append ( JST['tmpl/' + "edit"] (model.toJSON()) );
}
});
}
I thought that urlRoot when fetched would call /stores/ID_HERE, but right now it doesn't call that, it just calls /stores/, but I'm not sure why and how to fix this?
In devTools, here is the url it's going for:
GET http://localhost/stores/
This might not be the answer since it depends on your real production code.
Normally the code you entered is supposed to work, and I even saw a comment saying that it works in a jsfiddle. A couple of reasons might affect the outcome:
In your code you changed the Backbone.Model.url() function. By default the url function is
url: function() {
var base =
_.result(this, 'urlRoot') ||
_.result(this.collection, 'url') ||
urlError();
if (this.isNew()) return base;
return base.replace(/([^\/])$/, '$1/') + encodeURIComponent(this.id);
},
This is the function to be used by Backbone to generate the URL for model.fetch();.
You added a custom idAttribute when you declared your Store Model to be like the one in your DB. For example your database has a different id than id itself, but in your code you still use new Model({ id: id }); when you really should use new Model({ customId: id });. What happens behind the scenes is that you see in the url() function it checks if the model isNew(). This function actually checks if the id is set, but if it is custom it checks for that:
isNew: function() {
return !this.has(this.idAttribute);
},
You messed up with Backbone.sync ... lots of things can be done with this I will not even start unless I want to make a paper on it. Maybe you followed a tutorial without knowing that it might affect some other code.
You called model.fetch() "a la" $.ajax style:
model.fetch({
data: objectHere,
url: yourUrlHere,
success: function () {},
error: function () {}
});
This overrides the awesomeness of the Backbone automation. (I think sync takes over from here, don't quote me on that).
Reference: Backbone annotated sourcecode

Backbone reset a collection on fetch

This is my problem:
I have a container view that holds a collection.
On page load I get some models, populate this collection with them, then render the models
I fire and event
When this event fires, I want to make a call to my api (which returns models based on input parameters)
I then want to remove all existing models from the collection, repopulate with my new models, and then render the models
This is how I set up my model/collection/view
var someModel = Backbone.Model.extend({});
var someCollection = Backbone.Collection.extend({
model: someModel,
url: "api/someapi"
});
var someView = Backbone.View.extend({
events: {
"click #refresh": "refreshCollection"
},
initialize: function () {
this.collection.bind("reset", this.render, this);
},
render: function () {
// render stuff
},
refreshCollection: function (e) {
this.collection.fetch({data: {someParam: someValue});
this.render();
}
});
var app = function (models) {
this.start = function () {
this.models = new someCollection();
this.view = new someView({collection: this.models});
this.view.reset(models);
};
};
My point of interest is here:
refreshCollection: function (e) {
this.collection.fetch({data: {someParam: someValue});
this.render();
}
I pass in some paramaters, and my api returns a json array of models. I want to get rid of all existing models in the collection, and put all of my returned models into the collection, then update the view (with render())
I understand this is possible with collection.set, or collection.reset. Both of these take in an array of models. I don't have an array of models to pass in.
I tried:
this.collection.fetch({
data: {someParam: someValue},
success: function (response) {
doSomethingWith(response.models)
}
});
But I don't know what to do with the models when I get them.
Any pushed in the right direction would be appreciated!
From the fine manual:
fetch collection.fetch([options])
[...] When the model data returns from the server, it uses set to (intelligently) merge the fetched models, unless you pass {reset: true}, in which case the collection will be (efficiently) reset.
So you just need to include reset: true in the options and fetch will call reset to replace the collection's contents with the fetched models:
this.collection.fetch({
data: { ... },
reset: true
});

Knockout.js observable binding not updating (with JSON mapping)?

Background: I've got a single-page knockout.js app using the mapping plugin. The data is updated via Websocket JSON from the server. I can see that the app is successfully receiving the data (printing data in console) and when there are object removals/additions, the ViewModel updates no problem.
Problem: When a property of an object is updated from the server, it does not change the ViewModel. Do I need to return the object property somehow with each update?
Here are the relevant snippets of code:
var userMapping = {
"users": {
key: function(data) { return ko.utils.unwrapObservable(data.id); },
create: function(options) {
// for sortable ui access
return createUser(options.data);
}
}
};
var jobMapping = {
"jobs": {
key: function(data) { return ko.utils.unwrapObservable(data.id); },
create: function(options) {
// for sortable ui access
return createJob(options.data);
},
update: function(options) {
return createJob(options.data);
}
}
};
var createJob = function(job, user) {
// leaflet init
createJobIcon(job);
// general data mapping
var result = ko.mapping.fromJS(job);
return result;
};
self.engineModel.update = function(data) {
ko.mapping.fromJS(data, userMapping, self.engineModel);
};
And in the ViewModel:
<li data-bind="visible: canceled()==false, attr: {class: 'job-li canceled-'+canceled()+' started-'+started()+' hold-'+hold() }">
Thanks for the insight!
I've been able to fix the behaviour but I remain skeptical about the long-term impact from the decision.
I removed this line:
key: function(data) { return ko.utils.unwrapObservable(data.id); },
Now everything updates as it should. If anyone has thoughts about further processing I'm all ears (for example, does this affect performance?)

Backbone Sync with a mashup of models in Rails

I have a rails controller which sends a mash-up of models as a global json object. Something like this
{
dogs : { species: {}, ...},
cats : { food: {}, ...},
foxes : { },
...,
...
}
On my client side, I have all these entities neatly segregated out into different backbone models and backbone collections.
On some onchange event, I need to send a mashup of some model attributes back to the server as a HTTP POST request and the server sends a response which again spans values across a few models.
How do I setup Backbone.sync to deal with such an ajax scenario? I do not want to change the rails backend because its quite a steady implementation. Or do I make vanilla $.ajax requests through jQuery in one of my backbone views and handle it in a callback on ajax success/failure?
I think there are a couple of ways to do this via backbone. I think I'd start out with a model to represent the mashup:
var MashupModel = Backbone.Model.extend({
});
Then you can pass in any models like you would normally (or a collection for that matter):
var my_mash = new MashupModel({
dog: dogModel.toJSON(),
cat: catModel.toJSON(),
foxes: foxCollection.toJSON()
});
// do stuff if you need...
Then do what you want when the response comes back like normal:
my_mash.save({}, {
success: function(model, response) {
// do stuff here to put new data into the proper models / collections
},
error: function() { alert("I FAIL!"); }
});
That's all well and good... however, I think it would be better to push the above down into the MashupModel object instead of at the request level. Again, several ways:
var MashupModel = Backbone.Model.extend({
initialize: function(attrs) {
// can't remember the actual code, but something along the lines of:
_.each( attrs.keys, function(key) {
this.set(key, attrs.key.toJSON();
});
},
save: function(attrs, opts) {
var callback = opts.success;
opts.success = function(model, response) {
// do your conversion from json data to models / collections
callback(model, response);
};
// now call 'super'
// (ala: http://documentcloud.github.com/backbone/#Model-extend)
Backbone.Model.prototype.set.call(this, attrs, opts);
}
});
Or you could override toJSON (since backbone calls that to get the attrs ready for ajax):
// class definition like above, no initilize...
...
toJSON: function() {
// again, this is pseudocode-y
var attrs = {};
_.each( this.attributes.keys, function() {
attrs.key = this.attributes.key.toJSON();
});
return attrs;
}
...
// save: would be the same as above, cept you'd be updating the models
// directly through this.get('dogs').whatever...
Now, you can just do:
var my_mash = new MashupModel({
dog: dogModel,
cat: catModel,
foxes: foxCollection
});
// do some stuff...
my_mash.save({}, {
success: function(model, response) {
// now only do stuff specific to this save action, like update some views...
},
error: function() { alert("I FAIL!"); }
it would be possible, but may be difficult to modify backbone.sync to work with this structure. i'd recommend going with plain old jquery $.ajax requests. then on success, pull the info apart and populate your collections.
$.get("/whatever", function(data){
catCollection.reset(data.cats);
dogCollection.reset(data.dogs);
// etc
});
data = {};
data.cats = catCollection.toJSON();
data.dogs = dogCollection.toJSON();
// etc
$.post("/whatever", data);

Categories