Backbone JS save functionality - javascript

I have written a rails back end to my project and when you save or create a new record,among the status 200 and a json representation of the post that was saved.
When I do the following in bacbone:
modelObject = new App.Models.Post();
modelObject.set({title: 'asdasdas', content: 'asdadasdasdasdasd'});
if (modelObject.isValid()){
modelObject.save().then( ... )
}
How do I get the post object that is returned? (assuming the post is successful).
On the rails side, when I do #post.save I also do render json: #post, status: 200 on a successful save in the create action so there is a json object coming back, I just dot know how to access it on the backbone side.

The backbone docs describe few ways how can you get response from server after calling save() function.
For example:
You need to specify error and success callbacks:
var model = new App.Models.Post();
model.set({title: 'some title', content: 'some content'});
var options = {
success: function(model, response){
console.log('success handler');
model.set({id: response.id});
},
error: function(model, xhr){
console.log('error handler');
}
};
Specify wait option to wait response from server before set model attributes:
options.wait = true;
Need to call save function with specified options:
if (model.isValid()) {
model.save({}, options);
}

The modelObject.save() call will return a promise object. You should chain a .done() call to that and pass it in a function, like this:
modelObject.save().done(function(e) {
// handle your response here
});
You could also handle a failure the same way using the .fail() function. Chain them together like this:
modelObject.save().done(function(e) {
// handle your response here
}).fail(function(e) {
// handle failure here
});
Here's another way to write the same code:
var promise = modelObject.save();
promise.done(function(e) {
// handle your response here
});
promise.fail(function(e) {
// handle failure here
});
There is also a .always() that you could chain to always be called:
var promise = modelObject.save();
promise.done(function(e) {
// handle your response here
});
promise.fail(function(e) {
// handle failure here
});
promise.always(function(e) {
// always call this on success or failure
});

Related

How to use a default reject handler in Promise

I make Ajax requests with a Promise and usually handle errors the same way. So e.g. if a 404 happens, then I would just display a standard error message by default. But in some cases I want to do something else.
Note: I'm using ExtJS 4 to do the actual Ajax request, but this issue is not specific to ExtJS. ExtJS does not use Promises, so I'm basically converting their API to a Promise API.
This is the code:
var defaultErrorHandler = function(response) {
// do some default stuff like displaying an error message
};
var ajaxRequest = function(config) {
return new Promise(function(fulfill, reject) {
var ajaxCfg = Ext.apply({}, {
success: function(response) {
var data = Ext.decode(response.responseText);
if (data.success) {
fulfill(data);
} else {
defaultErrorHandler(response);
reject(response);
}
},
failure: function(response) {
defaultErrorHandler(response);
reject(response);
}
}, config);
Ext.Ajax.request(ajaxCfg);
});
};
// usage without special error handling:
ajaxRequest({url: '/some/request.json'}).then(function(data) {
// do something
});
// usage with special error handling:
ajaxRequest({url: '/some/request.json'}).then(function(data) {
// do something
}, function(response) {
// do some additional error handling
});
Now the problem: The "usage without special error handling" does not work, because if I do not provide a reject function, it will throw an error. To fix this, I am forced to provide an empty function, like so:
// usage without special error handling:
ajaxRequest({url: '/some/request.json'}).then(function(data) {
// do something
}, function() {});
Having to provide an empty function every time (and in my code base this will be hundreds of times) is ugly, so I was hoping there was a more elegant solution.
I also do not want to use catch() since that would catch ALL errors thrown, even if it happens in the fulfill function. But actual errors happening in my code should not be handled, they should appear in the console.
There is no such thing a "default error handler for all promises", unless you are looking to provide an unhandled rejection handler. That would however not be restricted to the promises for your ajax requests.
The simplest and best solution would be to just expose your defaultErrorHandler and have every caller explicitly pass it the then invocation on your promise. If they don't want to use it, they either need to provide their own special error handler or they will get a rejected promise. This solution provides maximum flexibility, such as allowing to handle the rejection further down the chain.
If that is not what you want to do, but instead require immediate handling of the ajax error, your best bet is to override the then method of your returned promises:
function defaultingThen(onfulfill, onreject = defaultErrorHandler) {
return Promise.prototype.then.call(this, onfulfill, onreject);
}
function ajaxRequest(config) {
return Object.assign(new Promise(function(resolve, reject) {
Ext.Ajax.request({
...config,
success: function(response) {
var data = Ext.decode(response.responseText);
if (data.success) {
resolve(data);
} else {
reject(response);
}
},
failure: reject,
});
}), {
then: defaultingThen,
});
}

Ember Understand execution flow between route/controller

I have a "box" route/controller as below;
export default Ember.Controller.extend({
initialized: false,
type: 'P',
status: 'done',
layouts: null,
toggleFltr: null,
gridVals: Ember.computed.alias('model.gridParas'),
gridParas: Ember.computed('myServerPars', function() {
this.set('gridVals.serverParas', this.get('myServerPars'));
this.filterCols();
if (!this.get('initialized')) {
this.toggleProperty('initialized');
} else {
Ember.run.scheduleOnce('afterRender', this, this.refreshBox);
}
return this.get('gridVals');
}),
filterCols: function()
{
this.set('gridVals.layout', this.get('layouts')[this.get('type')]);
},
myServerPars: function() {
// Code to set serverParas
return serverParas;
}.property('type', 'status', 'toggleFltr'),
refreshBox: function(){
// Code to trigger refresh grid
}
});
My route looks like;
export default Ember.Route.extend({
selectedRows: '',
selectedCount: 0,
rawResponse: {},
model: function() {
var compObj = {};
compObj.gridParas = this.get('gridParas');
return compObj;
},
activate: function() {
var self = this;
self.layouts = {};
var someData = {attr1:"I"};
var promise = this.doPost(someData, '/myService1', false); // Sync request (Is there some way I can make this work using "async")
promise.then(function(response) {
// Code to use response & set self.layouts
self.controllerFor(self.routeName).set('layouts', self.layouts);
});
},
gridParas: function() {
var self = this;
var returnObj = {};
returnObj.url = '/myService2';
returnObj.beforeLoadComplete = function(records) {
// Code to use response & set records
return records;
};
return returnObj;
}.property(),
actions: {
}
});
My template looks like
{{my-grid params=this.gridParas elementId='myGrid'}}
My doPost method looks like below;
doPost: function(postData, requestUrl, isAsync){
requestUrl = this.getURL(requestUrl);
isAsync = (isAsync == undefined) ? true : isAsync;
var promise = new Ember.RSVP.Promise(function(resolve, reject) {
return $.ajax({
// settings
}).success(resolve).error(reject);
});
return promise;
}
Given the above setup, I wanted to understand the flow/sequence of execution (i.e. for the different hooks).
I was trying to debug and it kept hopping from one class to another.
Also, 2 specific questions;
I was expecting the "activate" hook to be fired initially, but found out that is not the case. It first executes the "gridParas" hook
i.e. before the "activate" hook. Is it because of "gridParas"
specified in the template ?
When I do this.doPost() for /myService1, it has to be a "sync" request, else the flow of execution changes and I get an error.
Actually I want the code inside filterCols() controller i.e.
this.set('gridVals.layout', this.get('layouts')[this.get('type')]) to
be executed only after the response has been received from
/myService1. However, as of now, I have to use a "sync" request to do
that, otherwise with "async", the execution moves to filterCols() and
since I do not have the response yet, it throws an error.
Just to add, I am using Ember v 2.0
activate() on the route is triggered after the beforeModel, model and afterModel hooks... because those 3 hooks are considered the "validation phase" (which determines if the route will resolve at all). To be clear, this route hook has nothing to do with using gridParas in your template... it has everything to do with callling get('gridParas') within your model hook.
It is not clear to me where doPost() is connected to the rest of your code... however because it is returning a promise object you can tack on a then() which will allow you to essentially wait for the promise response and then use it in the rest of your code.
Simple Example:
this.doPost().then((theResponse) => {
this.doSomethingWith(theResponse);
});
If you can simplify your question to be more clear and concise, i may be able to provide more info
Generally at this level you should explain what you want to archive, and not just ask how it works, because I think you fight a lot against the framework!
But I take this out of your comment.
First, you don't need your doPost method! jQuerys $.ajax returns a thenable, that can be resolved to a Promise with Ember.RSVP.resolve!
Next: If you want to fetch data before actually rendering anything you should do this in the model hook!
I'm not sure if you want to fetch /service1, and then with the response you build a request to /service2, or if you can fetch both services independently and then show your data (your grid?) with the data of both services. So here are both ways:
If you can fetch both services independently do this in your routes model hook:
return Ember.RSVP.hash({
service1: Ember.RSVP.resolve($.ajax(/*your request to /service1 with all data and params, may use query-params!*/).then(data => {
return data; // extract the data you need, may transform the response, etc.
},
service2: Ember.RSVP.resolve($.ajax(/*your request to /service2 with all data and params, may use query-params!*/).then(data => {
return data; // extract the data you need, may transform the response, etc.
},
});
If you need the response of /service1 to fetch /service2 just do this in your model hook:
return Ember.RSVP.resolve($.ajax(/*/service1*/)).then(service1 => {
return Ember.RSVP.resolve($.ajax(/*/service2*/)).then(service2 => {
return {
service1,
service2
}; // this object will then be available as `model` on your controller
});
});
If this does not help you (and I really think this should fix your problems) please describe your Problem.

Backbone fetch success and error function issue

I've a fetch method on a collection. The callback function success and error is never called but the fetch happens correctly and fill the collection. It seems very strange.
var TweetsCollection= new Tweets();
TweetsCollection.fetch({
success:function (tweets){<---never called
alert("ok");
},
error:function(c){<---never called
alert("ko");
}
});
console.log(TweetsCollection);<---- collection correctly filled
and this is the fetch methof od TweetsCollection:
fetch: function(options) {
var collection = this;
var params = {
user_id: this.query,
page:this.page
};
cb.__call(
"statuses_userTimeline",
params,
function (reply) {
// console.log(reply);
collection.reset(reply);
// return reply;
}
);
}
You don't have to override the fetch method.
If you want to add extra logic to the sync process, you override the sync method.

ExpressJS why is my GET method called after my DELETE method?

In my express app, when the DELETE method below is called, the GET method is immediately called after and it's giving me an error in my angular code that says it is expected an object but got an array.
Why is my GET method being called when i'm explicitly doing res.send(204); in my DELETE method and how can I fix this?
Server console:
DELETE /notes/5357ff1d91340db03d000001 204 4ms
GET /notes 200 2ms - 2b
Express Note route
exports.get = function (db) {
return function (req, res) {
var collection = db.get('notes');
collection.find({}, {}, function (e, docs) {
res.send(docs);
});
};
};
exports.delete = function(db) {
return function(req, res) {
var note_id = req.params.id;
var collection = db.get('notes');
collection.remove(
{ _id: note_id },
function(err, doc) {
// If it failed, return error
if (err) {
res.send("There was a problem deleting that note from the database.");
} else {
console.log('were in delete success');
res.send(204);
}
}
);
}
}
app.js
var note = require('./routes/note.js');
app.get('/notes', note.get(db));
app.post('/notes', note.create(db));
app.put('/notes/:id', note.update(db));
app.delete('/notes/:id', note.delete(db));
angularjs controller
$scope.delete = function(note_id) {
var note = noteService.get();
note.$delete({id: note_id});
}
angularjs noteService
angular.module('express_example').factory('noteService',function($resource, SETTINGS) {
return $resource(SETTINGS.base + '/notes/:id', { id: '#id' },
{
//query: { method: 'GET', isArray: true },
//create: { method: 'POST', isArray: true },
update: { method: 'PUT' }
//delete: { method: 'DELETE', isArray: true }
});
});
** UPDATE **
To help paint the picture, here's the angular error i'm getting:
Error: [$resource:badcfg] Error in resource configuration. Expected response to contain an object but got an array http://errors.angularjs.org/1.2.16/$resource/badcfg?p0=object&p1=array
I'm assuming that i'm getting this error because my delete method is calling my get method (somehow) and the get method returns the entire collection.
Server side
You're removing an element from a collection in your delete function. This is done asynchronously and calling your callback when it's finished.
During this time, other requests are executed, this is why your GET request is executed before your DELETE request is finished.
The same happens in your get function, you're trying to find an element from a collection and this function is too asynchronous.
But this is server side only and it is fine, it should work this way, your problem is located client side.
Client side
If you want to delete your note after you got it, you will have to use a callback function in your angular controller which will be called only when you got your note (if you need help on that, show us your noteService angular code).
This is some basic javascript understanding problem, actions are often made asynchronously and you need callbacks to have an execution chain.
Maybe try doing something like this:
$scope.delete = function(note_id) {
var note = noteService.get({ id: note_id }, function()
{
note.$delete();
});
}
Your code doesn't make sense though, why is there a get in the $scope.delete? Why not do as simply as following:
$scope.delete = function(note_id) {
noteService.delete({ id: note_id });
}
Error
I think you get this error because of what your server sends in your exports.delete function. You're sending a string or no content at all when angular expects an object (a REST API never sends strings). You should send something like that:
res.send({
results: [],
errors: [
"Your error"
]
});

Json data from server not populating backbone model

I am new to Backbone.js and Require.js I am trying to get data from the server and store it in my backbone model. The problem is the data is not populating the model.
require.config({
paths:{
jquery:"libs/jquery-1.7.1.min",
underscore:"libs/underscore-min",
backbone:"libs/backbone-min",
models:"models",
views:"views",
collections:"collections"
},
shim:{
"backbone":{
"deps":["underscore","jquery"],
"exports":"Backbone"
},
"underscore":{
"exports":"_"
}
}
});
require (['routes'],function() {
new Router ();
});
The Model
define (['jquery','underscore','backbone'],function($,_,Backbone){
Video = Backbone.Model.extend ({
defaults:{
video_id:'',
video_name:'',
},
urlRoot:"/video",
});
});//end define
The Router
define(['backbone','models/Video'],function(Backbone){
Router = Backbone.Router.extend({
routes:{
"/":"index",
},
initialize:function (){
this.index ();
},
index:function ()
{
video = new Video ({id:"1Uw6ZkbsAH8"});
video.fetch();
console.log (video.toJSON());
}
});
});//end define
I check the network response and I am getting "{video_id:1,video_name:test_backbone}" as a response but when I console.log (video.toJSON()) i get - Object {id: "1Uw6ZkbsAH8", video_id: "", video_name: ""}. What am I doing wrong?
in your code logging is done right after the fetch is called. fetch makes an asycnhronous request to server for data. Your logging call is executed right away even before the network request is completed. this is one of the gotchas of event-based programming.
try calling console.log in the success callback.
video.fetch ( {
success: console.log(video.toJSON());
}
);

Categories

Resources