Backbone trying a put instead of a post - javascript

For some odd reason, Backbone is trying to put my model object instead of posting it to the server.
The error is an HTTP 500 error because Backbone is trying to put a model with no id (because I have made it undefined):
PUT /api/teams/undefined 500 285ms - 135b
Here's my code:
this.model.id = undefined;
this.model._id = undefined;
teamCollection.add(this.model);
this.model.save(null,
{
success: function (model) {
$('.top-right').notify({
message: {text: ' New team added! '},
type: 'info',
fadeOut: {
delay: 3500
}
}).show();
window.userHomeMainTableView.render();
}
,
error: function (model) {
teamCollection.remove(model);
$('.top-right').notify({
message: {text: ' Error adding team :( '},
type: 'danger',
fadeOut: {
delay: 3500
}
}).show();
}
});
even after "forcing" the model.id and model._id to be undefined, Backbone still tries to do an HTTP PUT. How can this be?

The syncing process internally uses Model#isNew to decide if it should PUT or POST. isNew is very simple minded:
isNew model.isNew()
[...] If the model does not yet have an id, it is considered to be new.
and that check is done using:
!this.has(this.idAttribute)
The has method is just a simple wrapper around attr:
has: function(attr) {
return this.get(attr) != null;
}
so these are irrelevant:
this.model.id = undefined;
this.model._id = undefined;
when Backbone is deciding to PUT or POST, those don't really remove the id attribute, they break your model.
I think you'd be better off copying the model (without the id) and saving the copy:
var data = m.toJSON();
delete data.id;
var copy = new Model(data);
Or better, create a whole new model, populate it with data, and then save the new model instance.
I think you'd need to unset the id attribute and manually remove the id property to make what you're trying to do work.

that's strange it looks like it should do a POST from your code. It looks like you can ovverride it though. From the second answer here
fooModel.save(null, {
type: 'POST'
});

use the following code snippet to set the id to undefined.
this.model.set('id',undefined');
although if it's a new model you don't need to do this. it will be undefined by default.
if you have used the idAttribute for defining the model and say it's value is userId then you need to do something like this.
this.model.set('userId',undefined');

Related

Ember data model arrays don't always have data

First time building an Ember app and I'm having difficulty with my models not resolving.
I have a set of Course models, each of which has an array of User models (hasMany, with async: true).
When I load the page, I see all of the API requests are correct (i.e. the client is making requests for all of the users in the array), and when I log the course model (i.e. when I do console.log(course)), I see the data in the console. However, when I do something like course.get('admins'), the array is empty.
The Course is not the model of the controller that needs all of this information (could this be why this is happening?), so I put the 'course' controller in the needs array.
In the end it's something like this:
var course = this.get('controllers.course').get('model'); // this is OK, has data
var admins = course.get('admins'); // this is not OK, has no data!
Even when I try something like:
course.get('admins').then(function (admins) {
console.log(admins);
});
There's still no data! What am I doing wrong?
Here's more of the relevant code:
var VideoDiscussionController = Ember.Controller.extend({
needs: ['course'],
submitComment: function () {
var user = this.get('session').get('currentUser'),
admins = this.get('controllers.course').get('model').get('admins'), // this is empty!
isAdmin = admins.isAny('id', user.get('id'));
var video = this.get('model'),
text = this.get('commentText'),
seconds = this.player.getCurrentTime() : 0,
comment = this.store.createRecord('comment', {
video: video,
text: text,
seconds: seconds,
author: user,
isAdmin: isAdmin
});
// POST comment
comment.save();
// Clear textbox
this.set('commentText', '');
video.get('comments').pushObject(comment);
}
});
I assume your model is something like this:
var CourseModel = DS.Model.extend({
admins: DS.hasMany('user', {
async: true
})
});
then, if you get your course model instance and if you do:
course.get('admins')
that line should trigger the AJAX call asking for the users.
Have you checked if such a request is called?
If not, could you provide some more code so that we can help you?

Trying to set the state in react via a loop and a ajax request

I am fooling around with a loop and a ajax request in react I cannot seem to get working. Its suppose to loop over, set the object array and then push that object array to the state for later use.
The issue is that I am failing at promises in general. I am using this concept from the react docs to set the state of a component upon mounting to return an array of "links".
/** #jsx React.DOM */
var Temp = {
object: new Array()
}
var CommentsRow = React.createClass({
getInitialState: function(){
return {
href: ''
}
},
componentDidMount: function(){
var self = this
this.props.comments.slice(0, 5).map(function(comment){
var postUrl = window.Development.API_URL + 'posts/' + comment.post_id
$.get(postUrl, function(post){
Temp.object.push(post.post.title);
if (self.isMounted()) {
self.setState({
href: Temp.object
});
}
});
});
},
render: function() {
console.log(this.state)
}
});
The gist of whats going on above is:
I have a bunch of comments coming in and I take the first five. From there I loop over each comment object and grab the title, creating my api link. With that I want to say get me the post based on this link, assuming it works we then want to set a temp object, this will create "five arrays" each going from a count of 1,2,3,4 and finally 5 elements.
from there we take that and set the state. This part works, but because its a ajax request the state out side the request is empty even if I use the if (isMounted()){ ... } option.
any idea how I can set the state doing something like this and still have access to it?
You either want async.js or promises to help manage multiple async actions. Async integrates a bit better with jQuery, so I'll show it with that.
componentDidMount: function(){
async.map(this.props.comments.slice(0, 5), function(comment, cb){
var postUrl = window.Development.API_URL + 'posts/' + comment.post_id;
$.get(postUrl, function(data){
cb(null, {title: data.post.title, href: ???});
});
}, function(err, posts){
// posts is an array of the {title,href} objects we made above
this.setState({posts: posts});
}.bind(this));
}

Waiting on collection data prior to rendering a subview

I am building what should be a fairly simple project which is heavily based on Ampersand's starter project (when you first run ampersand). My Add page has a <select> element that should to be populated with data from another collection. I have been comparing this view with the Edit page view because I think they are quite similar but I cannot figure it out.
The form subview has a waitFor attribute but I do not know what type of value it is expecting - I know it should be a string - but what does that string represent?
Below you can see that I am trying to fetch the app.brandCollection and set its value to this.model, is this correct? I will need to modify the output and pass through the data to an ampersand-select-view element with the correct formatting; that is my next problem. If anyone has suggestions for that I would also appreciate it.
var PageView = require('./base');
var templates = require('../templates');
var ProjectForm = require('../forms/addProjectForm');
module.exports = PageView.extend({
pageTitle: 'add project',
template: templates.pages.projectAdd,
initialize: function () {
var self = this;
app.brandCollection.fetch({
success : function(collection, resp) {
console.log('SUCCESS: resp', resp);
self.brands = resp;
},
error: function(collection, resp) {
console.log('ERROR: resp', resp, options);
}
});
},
subviews: {
form: {
container: 'form',
waitFor: 'brands',
prepareView: function (el) {
return new ProjectForm({
el: el,
submitCallback: function (data) {
app.projectCollection.create(data, {
wait: true,
success: function () {
app.navigate('/');
app.projectCollection.fetch();
}
});
}
});
}
}
}
});
This is only the add page view but I think that is all that's needed.
The form subview has a waitFor attribute but I do not know what type of value it is expecting - I know it should be a string - but what does that string represent?
This string represents path in a current object with fixed this context. In your example you've waitFor: 'brands' which is nothing more than PageView.brands here, as PageView is this context. If you'd have model.some.attribute, then it'd mean that this string represents PageView.model.some.attribute. It's just convenient way to traverse through objects.
There's to few informations to answer your latter question. In what form you retrieve your data? What do you want to do with it later on?
It'd be much quicker if you could ping us on https://gitter.im/AmpersandJS/AmpersandJS :)

Ember Data hasMany async observed property "simple" issue

Hopefully you can help me!
My Ember App (Ember 1.5.1) has two models (Ember Data beta7): Item and Tag. Item hasMany Tags.
I also have a computed property on tags which doesn't update. The computed property is a simple check to see if there are any tags (ie not empty). I've tried various things. I got as far as arrayComputed and then stopped. It should be reasonably trivial to check if an async related model has a count higher than 0! I've tried "tags.[]", "tags.#each", "tags.#each.status", "tags.#each.isLoaded", "tags.length" and various other things.
The computed property on the belongsTo works fine.
App.Item = DS.Model.extend({
tags: DS.hasMany("tag", inverse: "item", async: true),
hasTags: function() {
return !Em.isEmpty(this.get("tags"));
}.property("tags")
});
App.Tag = DS.Model.extend(
item: DS.belongsTo("item", inverse: "tags"),
hasItem: function() {
return !Em.isEmpty(this.get('))
}.property("item")
);
If I change it to read like this, then the log gets a line printed in it, so the promise is actually fulfilling and loading. I just don't know what to put on the computed property to make it reload when the promise has fulfilled. I feel like I'm missing something super obvious:
App.Item = DS.Model.extend({
tags: DS.hasMany("tag", inverse: "item", async: true),
hasTags: function() {
this.get("tags").then(function(tags) {
console.log("The tags are loding if this is printed");
});
return !Em.isEmpty(this.get("tags"));
}.property("tags")
});
[ edit ] 7 May 2014
Okay so I didn't fully explain the question properly the first go around I guess because I didn't fully understand what was wrong... I've continued the quesiton in the following stack overflow issue: Route's Model Hook with Ember Data "filter" not loading dependent computed property
I believe what you are looking for is this:
App.Item = DS.Model.extend({
tags: DS.hasMany("tag", inverse: "item", async: true),
hasTags: function() {
this.get("tags").then(function(tags) {
console.log("The tags are loding if this is printed");
});
return !Em.isEmpty(this.get("tags"));
}.property("tags.#each")
});
Because tags is an array, you want to be notified when tags are added or removed. the '.#each' will make it update when this happens.
Okay hopefully someone has a better answer than this, but this is how I've "gotten around it" for now, in my routes (I feel like this is an absolutely ridiculous way to do it), by adjusting the thing that relies on "hasTags" rather than adjusting hasTags itself.
Note I'm not even sure if I "fixed up" "hasTags" in Item whether or not it would trigger the filtered reference below to update...?
I think perhaps we need filter to be promise-aware in its block (ie so you can use properties to filter that are promises and it'll do the right thing):
App.TaggedItemsListRoute = App.ItemsRoute.extend({
model: function() {
var store = this.get("store");
var storePromise = store.find("item", { has_tags: true });
var filtered = store.filter("item", function(item) {
var tags = item.get("tags");
if tags.get("isFulfilled") {
return item.get("hasTags");
} else {
return tags.then(function() {
return item.get("hasTags");
});
}
});
return storePromise.then(function(response) {
return filtered;
});
}
});

How do you create a backbone model that might be invalid upon creation?

I'm trying to create a backbone model using:
var newUser = new App.User({
name: $('input[name=new_user_name]').val(),
email: $('input[name=new_user_email]').val()
});
with the following validation:
App.User = Backbone.Model.extend({
validate: function(attrs) {
errors = [];
if (_.isEmpty(attrs.name)) {
errors.push("Name can't be blank");
}
if (_.isEmpty(attrs.email)) {
errors.push("Email can't be blank");
}
return _.any(errors) ? errors : null;
},
...
});
However, when the textbox values are empty, the model can't be created because the validations fail.
Is there a way to create a model and run isValid() to check the values are correct? Or is there some other way to run validations before the model is created?
From the backbone model creation code, it appears it throws the exception Error("Can't create an invalid model") upon failing validation, but would it be possible to check validations before hand?
You can create unvalid model with silent: true, but in this way collection don`t trow event 'add'. But you can sent default option to function add and catch this option in validate()
collection.add({option:'unvalid_value'}, {validation: false});
Model = Backbone.Model.extend({
validate: function(attributes, options){
if (options.validation === false)
return;
//Validation code
//.....
}
});
This way you can create model with invalid hash)
My understanding is that you want to create a user model and that model gets populated with attributes based on some form input.
The way you have it set up now, to me doesn't seem very backbone-y.
var newUser = new App.User({
name: $('input[name=new_user_name]').val(),
email: $('input[name=new_user_email]').val()
});
To me it feels like you're mixing up stuff that happens in your views with your model which is the object it should represent. I would probably create a basic blank Model first, then a View that compliments this model and pass the model in.
User = Backbone.Model.extend ({
defaults: {
name: undefined,
email: undefined
}
)};
UserView = Backbone.View.extend ({
events: {
// like click on submit link, etc.
'click #submitButton': 'onSubmit'
},
initialize: function() {
this.user = this.model;
},
onSubmit: function() {
// run some validation on form inputs, ex. look for blanks, then...
if ( /* validation passes */ ) {
var nameEntered = $('input[name=new_user_name]').val();
var emailEntered = $('input[name=new_user_email]').val();
this.user.set({name:nameEntered, email:nameEntered});
}
}
});
// Do it!
var newUser = new User();
var newUserView = new UserView({model:newUser});
One great way to validate the form INPUT rather than than the Model attributes is to use jquerytools validator. It works with HTML5 attributes such as pattern=regex and type=email and makes it super easy.
You cannot create an Invalid model in backbone.
If you are creating a model and passing values to the constructor the no validation will be done.
If you are adding models to a collection via fetch then invalid data will cause the model validation to fail and the model will not be added to the collection. This should trigger the error event so you should be listening for it in case your server is providing invalid data back.
If you want to make a model but make sure the data is valid you must:
var mymodel = new MyModel();
mymodel.set(attrs);
Even if you try to pass {silent: false} during model instantation backbone will override it since it will not let you create an invalid model and does not throw errors. (which we arent discussing here)
You can see this by looking at the source, but also look at this github ticket

Categories

Resources