My app is Backbone.js for client-side, Express.js for back-end.
I have problems with syncing with all parts of my API, using the backbone model and collection(they use urlRoot: "/users").
I'm allowed to use only GET or POST, no PUT or DELETE.
*I'm not allowed to use more models*
Not allowed to use jQuery ajax
My API
add new user:
I need to make a POST to /users with JSON of new data. So I did it just fine with - this.model.save({new data...})
list all users:
My API for that, responses to GET /users, with the right handler - so, this.collection.fetch() - works fine.
Log-In:
My API accepts POST to /users/login for that. How can I add a function "logIn" to my model, that will use custom sync/pass options.url to sync - or any other way - that will POST to /users/login ?
Log-Out:
API accepts POST to /users/logout - how to send this request using my backbone model ?
User By ID:
Same question here for GET /users/:id
Update user:
POST /users/:id - same question again.
--- So actually, the question in short is ---
What is the best way (or the most "right"), to implement methods of a backbone model, that are similar to "model.save()" - but just POST/GET to a bit different path then urlRoot ?
You probably have a couple options here. One would be structuring your models in a way that supports the urls you want. For instance, have a User model and a Session model that deal with updating the user and managing the logged in state separately.
The other thing you should probably do is to use the url method in your models.
Something like this in your User model. (Note: using urlRoot instead of url here is identical, but this is the correct approach for anything more complicated that is needed in the url)
url : function() {
var base = "/users/";
if(this.isNew()) {
return base;
} else {
return base + this.get("id");
}
}
You could extend this same concept to work in your Session model for handling logout vs login based on if the Session is new or not.
Update:
If you must use the same model, the best thing would be to totally bypass the Backbone.sync method and write a custom AJAX call with success/error handlers that know how to clean things up.
login : function() {
$.post("/users/login", {success: function (response) {
// Update user as needed
}, error: function (xhr, response) {
// Handle errors
}
}
}
Related
I'm trying to figure out a way to cache my knockoutJS SPA data and I've been experimenting with amplifyJS. Here's one of my GET functions:
UserController.prototype.getUsers = function() {
var self = this;
return $.ajax({
type: 'GET',
url: self.Config.api + 'users'
}).done(function(data) {
self.usersArr(ko.utils.arrayMap(data.users, function(item) {
// run each item through model
return new self.Model.User(item);
}));
}).fail(function(data) {
// failed
});
};
Here's the same function, "amplified":
UserController.prototype.getUsers = function() {
var self = this;
if (amplify.store('users')) {
self.usersArr(ko.utils.arrayMap(amplify.store('users'), function(item) {
// run each item through model
return new self.Model.User(item);
}));
} else {
return $.ajax({
type: 'GET',
url: self.Config.api + 'users'
}).done(function(data) {
self.usersArr(ko.utils.arrayMap(data.users, function(item) {
// run each item through model
return new self.Model.User(item);
}));
}).fail(function(data) {
// failed
});
};
This works as expected, but I'm not sure about the approach I used, because it will also require extra work on the addUser, removeUser and editUser functions. And seeing as I have many more similar functions throughout my app, I'd like to avoid the extra code if possible.
I've found a way of handling things with the help of ko.extenders, like so:
this.usersArr = ko.observableArray().extend({ localStore: 'users' });
Then use the ko.extenders.localStore function to update the local storage data whenever it detects a change inside the observableArray. So on init it will write to the observableArray in case local storage data exists for users key and on changes it will update the local storage data.
My problem with this approach is that I need to run my data through the model and I couldn't find a way to do that from the localStore function, which is kept on a separate page.
Has any of you worked with KO and Amplify? What approach did you use? Should I use the first one or try a combination of the two and rewrite the extender in a way that it only updates the local storage without writing to the observableArray on init?
Following the discussion in the question's comments, I suggested to use native HTTP caching instead of adding another caching layer on the client by means of an extra library.
This would require implementing a conditional request scheme.
Such a scheme relies on freshness information in the Ajax response headers via the Last-Modified (or E-Tag) HTTP headers and other headers that influence browser caching (like Cache-Control: with its various options).
The browser transparently sends an If-Modified-Since (or If-None-Match) header to the server when the same resource (URL) is requested subsequently.
The server can respond with HTTP 304 Not Modified if the client's information is still up-to-date. This can be a lot faster than re-creating a full response from scratch.
From the Ajax request's point of view (jQuery or otherwise) a response works the same way, no matter if it actually came from the server or if it came from the browser's cache, the latter is only a lot faster.
Carefully adapting the server side is necessary for this, the client side on the other hand does not need much change.
The benefit of implementing conditional requests is reduced load on the server and faster response behavior on the client.
A specialty of Knockout to improve this even further:
If you happen to use the mapping plugin to map raw server data to a complex view model, you can define - as part of the options that control the mapping process - a key function. Its purpose is to match parts of your view model against parts of the source data.
This way parts of the data that already have been mapped will not be mapped again, the others are updated. That can help reduce the client's processing time for data it already has and, potentially, unnecessary screen updates as well.
FYI: There is main question on the bottom if you ever feel like my post is too long ;)
Im trying to build my first angularjs app and now Im stuck with collecting data via ajax from nodejs (express) server.
In front-end Im loading templates with angularjs routers and ng-view. In every route i have template and specific controller (this should be pretty basic thing right?).
OK here comes the wall... I was thinking to put $http.get() to load right stuff for the template from the nodejs server in the controller. With GET I could send variables like this.
$http.get('http://.../API', { params: { twitterData : true, needed : "data2" } } )
.success( function(result) {
// pass result to template.
});
And then on the server side get params like this.
router.get('/', function(req, res) {
this.params = req.query;
// here run every function and collect it to one object
// then return it for front-end ajax call.
res.send(JSON.stringify(collectedDataObj));
}
collectedDataObj could look something like this:
{ twitterData :
{ thisIs:twitterObject },
blog : {title: "...", content: "..." }
}
Collecting data would be by nested callbacks like introduced here http://book.mixu.net/node/ch7.html
So is this "collecting all data in back-end" best way to get data or should I send many ajax calls to collect data for one angular view?
Meaning one $http.get() for getting titter object and one for blog content etc.
And of course if you know some pass me links for good tutos/examples.
IMO, whenever possible it's best to get everything you can in one request. Trips back and forth to the server get pretty expensive.
Not the easiest issue to put into a title.
Anyhow, my app is built on nodejs/expressjsand has an API set up for the url:
EDIT: The current code I'm using is:
$scope.updateProduct = $resource('/api/updateProduct/:product/:param/:value',{},{
query: {method:'GET'},
post: {method:'POST'},
save: {method:'PUT', params: {brand: '#brand', param:'#param', value:'#value'}},
remove: {method:'DELETE'}
});
$scope.updateProduct.save({
product : $scope.post._id,
param: 'likes',
value: $scope.user._id
});
At present it calls /api/updateProduct instead of /api/updateProduct/<product>/<param>/<value> like it's supposed to / like it does when I perform $scope.updateProduct.get().
In my console I see (as an example):
PUT /api/updateBrand/Quay%20Eyewear%20Australia/userTags/sunglasses,%20classic 200 30ms - 2.31kb
However, the API isn't actually accessed/nothing happens. Interestingly, if I go to localhost:5000/api/updateBrand/Quay%20Eyewear%20Australia/userTags/sunglasses,%20classic in my browser, it posts the correct data and updates the product in my database, so it's definitely an error with the way the $resource is being called.
As you can see in ngResource docs, $resource receive 3 parameters:
$resource(url[, paramDefaults][, actions]);
You are passing your action as a parameter.
The correct version would be:
$scope.updateProduct = $resource('/api/updateProduct/:product/:param/:value',{}, {'save':{method:'POST'}});
Note that it isn't even necessary, because when you use $resource you already create the default methods:
{
'get': {method:'GET'},
'save': {method:'POST'},
'query': {method:'GET', isArray:true},
'remove': {method:'DELETE'},
'delete': {method:'DELETE'}
};
First of all, your have defined the save() to have a parameter called "brand", but in your url template and in the call to save(), you are using "product". I guess it's a typo.
You say when you are using browser to visit the url, it works well; but when angular is make a PUT request to the same url, nothing is happening. This indicates that your backend is configure to process only GET requests for this particular url pattern. Therefore, you need to make sure that your backend is accepting PUT requests.
I was struggling with this issue and was able to pass parameters to the resource by doing the equivalent call below (notice the '$' before 'save').
$scope.updateProduct.$save({
product : $scope.post._id,
param: 'likes',
value: $scope.user._id
});
I also did not define a 'save' method in the resource. According to Angular docs:
"Calling these methods (meaning non-GET methods) invoke $http on the url template with the given method, params and headers. When the data is returned from the server then the object is an instance of the resource type and all of the non-GET methods are available with $ prefix. This allows you to easily support CRUD operations (create, read, update, delete) on server-side data."
Hello Backbone ninjas,
This is my first time using Backbone - so please excuse my "noob"ness.
In my functionality (part of a larger app),I have a Backbone View vA, backed by a model mA (as it should be ) and the server side is in Spring MVC having annotated Spring controller methods with #RequestBody and #ResponseBody. I've got Jackson working fine with Spring.
Now, in the app,
Backbone.Model
|_ BaseModel (custom base model for our app)
|_ mA (my model)
mA has its own endpoint and it Backbone sucessfully calls that when making a PUT request i.e., when I call save() from a submit button event handler from View vA like so:
this.model.save({
success : function(){
alert('Request submitted successfully');
},
error : function(){
alert('Something awful happened.');
}
});
Our BaseModel has the following:
define([], function() {
window.BaseModel = Backbone.Model.extend({
......
});
onSyncError : function(model, response) {
switch (response.status) {
case 403:
[...//some more code ]
default:
alert(bundle.getDefault('HTTP_RESP_OTH') + response.status);
}
},
onSyncSuccess : function(model, response) {
alert('Sync done! ');
},
sync : function(method, model, options) {
options.error = this.onSyncError;
Backbone.sync.call(this, method, model, options);
....//some more stuff.
},
}
Spring controller method:
#RequestMapping(value="/resource/xyz/{id}.json", method = RequestMethod.PUT, consumes = {"application/json"}
, produces = {"application/json"})
#ResponseBody
public Map<String,String> methodX(#RequestBody XyzDTO xyzDTO){
....
map.put("msg", "success");
return map;
}
Also, right before I make the save call, I modify a few Model attributes, since the server side DTO has a different structure like so:
this.model.unset("abc",{ silent: true });
this.model.set( { abc: {id : "2",xyz:{ ... //more code } );
The issue is, calling save() generates a PUT request and successfully calls the Spring endpoint handler, but I get a response code 200 (which is what I expect),
but when I trace the call with Firebug, it goes into the onSyncError method and gives me an error message (because of the "default" case in it).
The Backbone doc says : "When returning a JSON response, send down the attributes of the model that have been changed by the server, and need to be updated on the client". Well, I don't need to update the model on the client side, its one of the last screens and I just need to tell the user of a success / error and
redirect him to a main page/dashboard.
I read up some more, and it seems code 200 as response is not sufficient - there might be JSON parsing errors causing the sync to fail.
I checked the response in Firebug, and the response JSON looks like {"msg":"Success"}.
So, what could be going wrong?
Backbone.Model.save() expects the response from the server to be an updated hash of the model's values. If your response is of the kind {"msg":"Success"}, Backbone may fail to sync with your model. Basically, it interprets your HTTP 200 JSON response as the model's attributes and tries to sync the model accordingly.
You might try either 1) Making your Spring controller path return a JSON-ified model response, 2) Return a plain 200 with an empty response body or 3) write a custom parse method which looks for responses with the {"msg":"Success"} format and responds differently.
Thanks for your time. I was finally able to get around the problem by using $.ajax to make the PUT request, thereby bypassing the whole Backbone sync thingy. My success handler in the ajax callback handles the response and there are no more sync errors (since its not being called anyways) :)
I'll share my experience with the same problem;
custom base-model and
calling model.save and no success event fired.
My problem was with a custom set function in the base model which didnt return "this".
If you peek at the backbone source code for model-save you'll find this snippet:
options.success = function(resp) {
// Ensure attributes are restored during synchronous saves.
model.attributes = attributes;
var serverAttrs = model.parse(resp, options);
if (options.wait) serverAttrs = _.extend(attrs || {}, serverAttrs);
if (_.isObject(serverAttrs) && !model.set(serverAttrs, options)) {
return false;
}
if (success) success(model, resp, options);
model.trigger('sync', model, resp, options);
};
The !model.set(serverAttrs, options) failed in my case and the save-function returned false before triggering any events.
Maybe this wasn't your problem but hopefully it'll help someone else out there...
Validate your JSON response..
In my case I had an extra comma (,)..
Nearly in-valid response may cause this issues
Say I have the following ember-data model:
App.Person = DS.Model.extend({
firstName: DS.attr('string'),
lastName: DS.attr('string'),
starred: DS.attr('boolean')
});
This communicates with a Rails app with the following pretty standard CRUD API:
GET /people - get a list of people
POST /people - create a new person
GET /people/id - get a specific person
PUT /people/id - update a specific person
DELETE /people/id - delete a specific person
This all maps to Ember-Data with the standard Store/Adapter.
However, lets say that in order to "star" or "unstar" a person, the API doesn't let us do this by the standard update action. There's a specific API endpoint for this action:
POST /people/id/star - mark a person as "starred"
POST /people/id/unstar - mark a person as "unstarred"
How do I fit this API in with Ember Data?
It looks like I'd need to extend DS.Store and DS.RESTAdapter somehow, but I'm not sure of the best approach to make them aware of these different actions. It also feels a bit wrong that a generic Adapter for the app has to be aware of starring people.
Note that I have no control over the API, so I can't make POST /people/id aware of "starring" so that it would fit in with a standard update.
Been a while and it may not have been possible at the time, but you can call the ajax method of your adapter directly:
YourApp.Store.prototype.adapter.ajax(url, action, {data: hashOfParams})
For example:
YourApp.Store.prototype.adapter.ajax('/products', 'GET', {data: {ids: [1,2,3]}})
For your question:
YourApp.Store.prototype.adapter.ajax('/people' + id + '/star','POST')
Edit - using buildURL is useful, particularly if you've set a namespace on your adapter:
url = YourApp.Store.prototype.adapter.buildURL('people',id) + '/star'
Edit 2 - You can also get the adapter with container.lookup('adapter:application'), which is useful if you don't have global access to the app (ex ES6 modules / ember-cli)
Edit 3 - The above refers to an outdated version of Ember / Ember-CLI. Now I define this function in a mixin -
customAjax: (method, type, id, action, hash = null) ->
#note that you can now also get the adapter from the store -
#adapter = #store.adapterFor('application')
adapter = #container.lookup('adapter:application')
url = adapter.buildURL(type, id) + '/' + action
hash['data'] = $.extend({}, hash) if hash #because rails
adapter.ajax(url, method, hash).then (result) =>
return result
And call it like so -
#customAjax('PUT', 'modelClass', modelId, 'nonCRUDActionName', optionalHashOfAdditionalData).then (response) =>
#do something with response
I went through the same issue, and solved it using a modified version of what is explained in here:
Non-crud rails actions in Ember.js
Basically, you need to use a Jquery AJAX call, the important part for me was to change the contentType in order to get the auth data to be sent in the request headers, rather than in form-data (the default, if you include data in your call). Also, make sure to give the proper context in the AJAX part, otherwise 'this' won't work in the 'success' callback:
App.ItemController = Ember.ObjectController.extend
actions:
starItem: ->
controller = this
item = #get 'model'
id = item.id
token = #auth.get 'authToken'
$.ajax
type: 'PUT'
context: controller
data: '{ "auth_token": "' + token + '"}'
url: '/api/v1/items/' + id + '/star'
contentType: 'application/json; charset=UTF-8'
dataType: 'json'
success: ->
if #get 'item.starred_by_user'
#set 'model.starred_by_user', false
item.decrementProperty('total_stars')
else
#set 'model.starred_by_user', true
item.incrementProperty('total_stars')
Maybe a good start: https://github.com/beautifulnode/ember.request
Did not tryed it yet, but seems promising in your case...
I think, that in CQRS therms star and unstar are commands. So you should declare model PersonStar and work with it.
In the discussion group, Yehuda mentioned this isn't possible yet.