Not fetching correct url issue - javascript

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

Related

Backbone view/template fails to load from REST

I have properly coded a simple REST api and several backbone models. My parent model is called Topic and child model called Questions.
I'm trying to call a get method on the REST api and display the received Topic object to the user in a presentable manner. I am receiving the json (can be seen in the network tab on Chrome), but it is not getting sent to the view correctly.
Model:
var Topic = Backbone.Model.extend({
urlRoot: ROOT + '/topic',
idAttribute: 'topicId',
initialize: function () {
this.questions = new Questions([], {parent: this});
},
toJSON: function () {
var json = Backbone.Model.prototype.toJSON.call(this);
json.questions = this.questions.toJSON();
return json;
}
});
var Topics = Backbone.Collection.extend({
model: Topic,
url: ROOT + 'topic',
parse: function (response) {
return response.results;
}
})
REST URL:
http://localhost/Project/index.php/rest/resource/topic/
Backbone View: This is where I think the error is...(console log below prints an empty object)
var TopicListView = Backbone.View.extend({
el: '.page',
render: function () {
var that = this;
var topics = new Topics();
topics.fetch({
success: function (topics) {
console.log(topics);
var template = _.template($('#topic-list-template').html(), {topics: topics.models});
that.$el.html(template);
}
})
}
});
Using the above functions:
var topic = new Topic();
topic.fetch();
topicListView = new TopicListView();
var Router = Backbone.Router.extend({
routes: {
"": "home"
}
});
var router = new Router;
// render topic list for 'home'
router.on('route:home', function () {
topicListView.render();
});
Edit: Solution: Overriding the parse function in the collection proved to be the error. I wonder why...
The argument topics in your success handler is shadowing the variable topics.
The argument contains the parsed JSON response, not the Backbone Collection. You don't need that, so you can remove the argument.
The reference to topics will now be to your Collection, so topics.models will have the value you expect.
topics.fetch({
success: function () { // argument removed here so `topics` is no longer shadowed
var template = _.template($('#topic-list-template').html(), { topics: topics.models });
that.$el.html(template);
}
})

backbone.js - model.save() generate wrong PUT url path

I hate to ask these strange problems but couldn't able to avoid this one.
I have "Option" view with "Option" model passed as a parameter when creating.
var optionView = new OptionView({ model: option });
this.$el.find('div#optionsBoard').append( optionView.render().el );
In this view, when the user clicks on "Vote" button, the "voteCount" attribute of the model will be incremented.
events: { 'click .button-vote': 'processVote' },
processVote: function (e) {
var voteCounted = this.model.get('voteCount');
this.model.set('voteCount', voteCounted++);
console.log(this.model.id); // has a Id value
console.log(this.model.isNew()); // false
this.model.save(); // occurs problem here
e.preventDefault();
},
The problem occurs when I save the model back to the server as following:
PUT http://localhost:13791/api/options/ 404 (Not Found)
Yes, this url actually isn't existed on my REST API server. But I believe the correct path of PUT URL to update the model should be as following:
PUT http://localhost:13791/api/options/id_of_the_entity_to_be_updated
When I test this PUT url (http://localhost:13791/api/options/id_of_the_entity_to_be_updated) with Postman Rest client, it works perfectly.
So I think the problem occurs because Backbone model.save() method does not add the id_of_the_entity_to_be_updated to the PUT url.
Please, suggest me something how should I solve this problem.
As additional description, this is my "option" model setup code.
define([
'backbone'
], function (Backbone) {
var Option = Backbone.Model.extend({
idAttribute: "_id",
defaults: {
name: '',
location: '',
link: '',
voteCount: 0,
expiredDate: Date.now(),
imageName: ''
},
url: '/api/options/',
readFile: function(file) {
var reader = new FileReader();
// closure to capture the file information.
reader.onload = ( function(theFile, that) {
return function(e) {
that.set({filename: theFile.name, data: e.target.result});
that.set({imageUrl: theFile.name});
console.log(e.target.result);
};
})(file, this);
// Read in the image file as a data URL.
reader.readAsDataURL(file);
}
});
return Option;
});
Problem found
My bad. In the "Option" model setup, it should be "urlRoot" instead of "url".
In your model you should use urlRoot instead url:
urlRoot: '/api/options/'

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));

Ember Data - TypeError: Object has no method 'eachRelationship'

So, I'm trying to build routes in my Ember application dynamically with data from an API endpoint, /categories, with Ember Data. In order to do this, I'm adding a didLoad method to my model, which is called by the controller and set to a property of that controller. I map the route to my router, and all that works fine. The real trouble starts when I try to set up a controller with a content property set by data from the server retrieved by findQuery.
This is the error:
TypeError {} "Object /categories/548/feeds has no method 'eachRelationship'"
This is the code:
window.categoryRoutes = [];
App.Categories = DS.Model.extend({
CATEGORYAFFINITY: DS.attr('boolean'),
CATEGORYID: DS.attr('number'),
CATEGORYNAME: DS.attr('string'),
CATEGORYLINK: function () {
var safeUrl = urlsafe(this.get('CATEGORYNAME'));
categoryRoutes.push(safeUrl);
return safeUrl;
}.property('CATEGORYNAME'),
didLoad: function () {
var categoryLink = this.get('CATEGORYLINK');
var categoryId = this.get('CATEGORYID');
App.Router.map(function () {
this.resource(categoryLink, function () {
// some routes
});
});
App[Ember.String.classify(categoryLink) + 'Route'] = Ember.Route.extend({
setupController: function(controller, model) {
// source of error
this.controllerFor(categoryLink).set(
'content',
this.store.findQuery('/categories/' + categoryId + '/feeds', {
appid: 'abc123def456',
lat: 39.75,
long: -105
})
);
}
});
}
});
Any 'halp' is appreciated!
Also, if I'm doing this completely wrong, and there's a more Ember-like way to do this, I'd like to know.
I figured this out. I got this error because I was passing in a string instead of a real 'type' from the App.Helpers object to an extract method in some custom RESTAdapter code I had overridden.
The solution is to pass in the corresponding model helper in App.Helpers using my custom type name.
Something like this in the overridden RESTAdapter.serializer.extractMany method:
var reference = this.extractRecordRepresentation(loader, App.Helpers[root], objects[i]);

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