Backbone model.save() is sending PUT instead of POST - javascript

I call save using this:
console.log(this.model.isNew());
console.log(this.model);
this.model.save({}, {
success: function (model, response, options) {
console.log(response);
},
error: function (model, xhr, options) {
console.log(xhr.result.Errors);
}
});
The isNew() returns false. But the output of this.model has an ID of 0. (this.model.id is 0 as well)
My url is url: ROOTAREA + "/Expenses/Entry/",
Updating works fine, and uses PUT as expected.
Edit : here's part of my model:
defaults: function () {
return {
DocumentDate: "",
JobNo_: "",
PhaseCode: "",
WorkTypeCode: "",
Description: "",
Quantity: 0,
UnitCost: 0,
ExpenseCurrencyCode: "",
ReimbursementCurrencyCode: "",
UnitofMeasureCode: "DIEM",
LineNo_: 0
};
},
idAttribute: "LineNo_",

ID should not even exist for a new entry.
The issue is in the part you didn't show - in the part where you instantiate, create and populate the model.
Here is a quote from the Backbone documentation:
If the model does not yet have an id, it is considered to be new.
It is clear from your code that you are assigning an id attribute.
Your backend should be doing that.
And since you are doing it on a client, backbone presumes it it not new, and uses PUT

The above answers are correct in that if the model you are .save'ing has an id attribute backbone will do a PUT rather than a POST.
This behavior can be overridden simply by adding type: 'POST' to your save block:
var fooModel = new Backbone.Model({ id: 1});
fooModel.save(null, {
type: 'POST'
});

You can specify the ID in defaults, just make sure it's set to null (isNew will be set to true).
In your case it must be
LineNo_: null

Related

Add multiple records to model's collection Sailsjs

I have the following models in my Sailsjs application with a many-to-many relationship:
event.js:
attributes: {
title : { type: 'string', required: true },
description : { type: 'string', required: true },
location : { type: 'string', required: true },
maxMembers : { type: 'integer', required: true },
currentMembers : { collection: 'user', via: 'eventsAttending', dominant: true },
creator : { model: 'user', required: true },
invitations : { collection: 'invitation', via: 'eventID' },
tags : { collection: 'tag', via: 'taggedEvents', dominant: true },
lat : { type: 'float' },
lon : { type: 'float' },
},
tags.js:
attributes: {
tagName : { type: 'string', unique: true, required: true },
taggedEvents : { collection: 'event', via: 'tags' },
},
Based on the documentation, this relationship looks correct. I have the following method in tag.js that accepts an array of tag strings, and an event id, and is supposed to add or remove the tags that were passed in:
modifyTags: function (tags, eventId) {
var tagRecords = [];
_.forEach(tags, function(tag) {
Tag.findOrCreate({tagName: tag}, {tagName: tag}, function (error, result) {
tagRecords.push({id: result.id})
})
})
Event.findOneById(eventId).populate('tags').exec(function(error, event){
console.log(event)
var currentTags = event.tags;
console.log(currentTags)
delete currentTags.add;
delete currentTags.remove;
if (currentTags.length > 0) {
currentTags = _.pluck(currentTags, 'id');
}
var modifiedTags = _.pluck(tagRecords, 'id');
var tagsToAdd = _.difference(modifiedTags, currentTags);
var tagsToRemove = _.difference(currentTags, modifiedTags);
console.log('current', currentTags)
console.log('remove', tagsToRemove)
console.log('add', tagsToAdd)
if (tagsToAdd.length > 0) {
_.forEach(tagsToAdd, function (tag) {
event.tags.add(tag);
})
event.save(console.log)
}
if (tagsToRemove.length > 0) {
_.forEach(tagsToRemove, function (tagId) {
event.tags.remove(tagId)
})
event.save()
}
})
}
This is how the method is called from the event model:
afterCreate: function(record, next) {
Tag.modifyTags(tags, record.id)
next();
}
When I post to event/create, I get this result: http://pastebin.com/PMiqBbfR.
It looks as if the method call itself is looped over, rather than just the tagsToAdd or tagsToRemove array. Whats more confusing is that at the end, in the last log of the event, it looks like the event has the correct tags. When I then post to event/1, however, the tags array is empty. I've also tried saving immediately after each .add(), but still get similar results.
Ideally, I'd like to loop over both the tagsToAdd and tagsToRemove arrays, modify their ids in the model's collection, and then call .save() once on the model.
I have spent a ton of time trying to debug this, so any help would be greatly appreciated!
There are a few problems with your implementation, but the main issue is that you're treating certain methods--namely .save() and .findOrCreate as synchronous methods, when they are (like all Waterline methods) asynchronous, requiring a callback. So you're effectively running a bunch of code in parallel and not waiting for it to finish before returning.
Also, since it seems like what you're trying to do is replace the current event tags with this new list, the method you came up with is a bit over-engineered--you don't need to use event.tags.add and event.tags.remove. You can just use plain old update.
So you could probably rewrite the modifyTags method as:
modifyTags: function (tags, eventId, mainCb) {
// Asynchronously transform the `tags` array into an array of Tag records
async.map(tags, function(tag, cb) {
// For each tag, find or create a new record.
// Since the async.map `cb` argument expects a function with
// the standard (error, result) node signature, this will add
// the new (or existing) Tag instance to the resulting array.
// If an error occurs, async.map will exit early and call the
// "done()" function below
Tag.findOrCreate({tagName: tag}, {tagName: tag}, cb);
}, function done (err, tagRecords) {
if (err) {return mainCb(err);}
// Update the event with the new tags
Event.update({id: eventId}, {tags: tagRecords}).exec(mainCb);
});
}
See the full docs for async.map here.
If you wanted to stick with your implementation using .add and .remove, you would still want to use async.map, and do the rest of your logic in the done method. You don't need two .save calls; just do run all the .add and .remove code first, then do a single .save(mainCb) to finish it off.
And I don't know what you're trying to accomplish by deleting the .add and .remove methods from currentTags (which is a direct reference to event.tags), but it won't work and will just cause confusion later!

Backbone not making a put request with save() after save

I am experiencing a really interesting problem with backbone, I have a function like this in one of my views:
addpeople :function(){
var authArray = _.clone(this.model.get("authorizedUsers"));
var values = $(".add-input").val().split(",");
values.forEach(function(value) {
authArray.push(value);
});
this.model.set("authorizedUsers" , authArray);
this.model.save();
},
this function gets called when a button is clicked. This version of the function triggers a change event because I am cloning my array, but for some reason this.model.save()never gets called, aka the server never receives a PUT request. When I refresh the page I go back to the old state of the model..
However if I dont clone the array and change the function to, this:
addpeople :function(){
var authArray = this.model.get("authorizedUsers");
var values = $(".add-input").val().split(",");
values.forEach(function(value) {
authArray.push(value);
});
this.model.set("authorizedUsers" , authArray);
this.model.save();
},
This time the PUT request is sent successfully, but the page is not re-rendered because a change event is not triggered. When I refresh the page I can see the updated model..
I know that I can manually trigger a change event in the second example but I am more curious about why my this.model.save() is not called in the first example..
To help you understand the problem more my model looks something like:
var PostModel = Backbone.Model.extend({
urlRoot : '/tweet',
idAttribute: '_id',
defaults:{
name: '',
comments: [],
tags: [],
authorizedUsers: [],
postedBy : '',
dateCreated: ''
},
});
and my node.js update function looks like:
exports.updateTweet = function(req,res){
console.log("Getting called ! ")
var update = req.body;
var id = req.url.split("/")[2];
Post.update({ _id: id }, { $set: { authorizedUsers: req.body.authorizedUsers }}, function (err, post) {
if (err) return handleError(err);
});
res.end();
};
The reason why change didn't trigger for your second example is because it is the same object and Backbone ignore it. Hence no change event triggered.
As for why the first one failed; do you have validator for your model? May be something that validating for empty string perhaps? val() can return an empty string and split() on empty string will return [""]
Also, your defaults should be a function otherwise all your model would have the same instance of comments, tags and authorizedUsers
From Backbone doc.
Remember that in JavaScript, objects are passed by reference, so if you include an object as a default value, it will be shared among all instances. Instead, define defaults as a function.
Arrays are object too.
var PostModel = Backbone.Model.extend({
urlRoot : '/tweet',
idAttribute: '_id',
defaults: function(){
return {
name: '',
comments: [],
tags: [],
authorizedUsers: [],
postedBy : '',
dateCreated: ''
}
}
});
Lastly, array.forEach() is not available on IE8 and older.

Load data into a Backbone collection from JSON file?

I'm trying to load some data into a Backbone Collection from a local JSON file, using this very basic code:
window.Student = Backbone.Model.extend({
});
window.Students = Backbone.Collection.extend({
model: Student,
});
window.AllStudents = new Students();
AllStudents.fetch({ url: "/init.json"});
console.log('AllStudents', AllStudents);
In the console statement, AllStudents is empty. But init.json is definitely being loaded. It looks like this:
[
{ text: "Amy", grade: 5 },
{ text: "Angeline", grade: 26 },
{ text: "Anna", grade: 55 }
]
What am I doing wrong?
UPDATE: I've also tried adding a reset listener above the .fetch() call, but that's not firing either:
AllStudents.bind("reset", function() {
alert('hello world');
});
AllStudents.fetch({ url: "/init.json"});
No alert appears.
UPDATE 2: Trying this script (reproduced here in full):
$(function(){
window.Student = Backbone.Model.extend({
});
window.Students = Backbone.Collection.extend({
model: Student,
});
window.AllStudents = new Students();
AllStudents.url = "/init.json";
AllStudents.bind('reset', function() {
console.log('hello world');
});
AllStudents.fetch();
AllStudents.fetch({ url: "/init.json", success: function() {
console.log(AllStudents);
}});
AllStudents.fetch({ url: "/init.json" }).complete(function() {
console.log(AllStudents);
});
});
Only one console statement even appears, in the third fetch() call, and it's an empty object.
I'm now absolutely baffled. What am I doing wrong?
The JSON file is being served as application/json, so it's nothing to do with that.
The attribute names and non-numeric attribute values in your JSON file must be double quoted (" ") . Single quotes or no-quotes produces errors and response object is not created that could be used to create the models and populate the collection.
So. If you change the json file content to :
[
{ "text": "Amy", grade: 5 },
{ "text": "Angeline", grade: 26 },
{ "text": "Anna", grade: 55 }
]
you should see the non-empty collection object.
You can change your code to see both success and failure as below:
AllStudents.fetch({
url: "/init.json",
success: function() {
console.log("JSON file load was successful", AllStudents);
},
error: function(){
console.log('There was some error in loading and processing the JSON file');
}
});
For more details, probably it will be a good idea to look in to the way ajax response objects are created.
I/O operations in javascript are almost always asynchronous, and so it is with Backbone as well. That means that just because AllStudents.fetch has returned, it hasn't fetched the data yet. So when you hit your console.log statement, the resources has not yet been fetched. You should pass a callback to fetch:
AllStudents.fetch({ url: "/init.json", success: function() {
console.log(AllStudents);
}});
Or optionally, use the new promise feature in jQuery (fetch will return a promise):
AllStudents.fetch({ url: "/init.json" }).complete(function() {
console.log(AllStudents);
});
fetch() returns a 'success' notification as already stated, but that just means that the server request was successful. fetch() brought back some JSON, but it still needs to stuff it into the collection.
The collection fires a 'reset' event when it's contents have been updated. That is when the collection is ready to use...
AllStudents.bind('reset', function () { alert('AllStudents bind event fired.'); });
It looks like you had this in your first update. The only thing I did differently was to put fetch() in front of the event binding.
I think you need to add {add:true} to the options of fetch,
if you assigned the fetch to a variable, you would get the result as well,
but then its not inside the collection you wanted

JavaScript: Backbone.js fetch json and load it into a collection of models?

So far i have the following code, but it doesn't seem to be working, and I don't know when the asynchronous fetch is completed:
var item = Backbone.Model.extend({
defaults: {
id: 0,
an_id: 0,
strval: null,
th_id: 0,
text: null
},
url: 'page.php',
options: {
success: function(data) {
alert('s: ' + dump(data));
// the dump function is my way of dumping objects into a string,
// use console.log if you want, as I have that disabled
},
error: function(x, t, e) {
alert('e: ' + t + ', ' + e);
}
}
});
var coll = Backbone.Collection.extend({
model: item
});
var options = new Options();
Backbone.sync("create", coll, item.options); // 'undefined' is not an object (evaluating c.url) in backbone-min.js
Update
I have modified the original code to what I have now, and the backend can now tell the difference between New, Update, Save and Delete requests.
I still cannot find out how to populate the collection coll.
Backbone.Collection is for keeping multiple items - you seem to be trying to have your Collection "inherit" from your model, which isn't the right approach.
Collections are ordered sets of models. You can bind "change" events
to be notified when any model in the collection has been modified,
listen for "add" and "remove" events, fetch the collection from the
server, and use a full suite of Underscore.js methods.
you can add a success handler to your fetch call. try this:
coll.fetch({
success: function() {
alert("success");
console.log(coll.toJSON());
},
error: function(){
alert("error")}
});

How to get id on a successful model.save()?

Have checked some backbone.js tutorials and can't
understand how to get model id from the server within
the model saving process. I have a model:
var Game = Backbone.Model.extend({
defaults: {
name: '',
releaseDate: ''
},
url: function(){
return '/data.php';
}
});
How to implement getting the id algorithm? It seams to me, there should
be a kind of callback function, but can't realise where to put it.
See Backbone's documentation on model save.
You can pass a success callback function to save, something like this:
var game = new Game({
name: 'Duke Nukem 3D',
releaseDate: '1996'
});
game.save({}, {
success: function(model, response) {
// get model id from response?
}
);

Categories

Resources